taler-merchant-httpd_helper.c (39508B)
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 (GNUNET_OK != 357 GNUNET_JSON_parse (location, 358 spec, 359 &ename, 360 &eline)) 361 { 362 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 363 "Invalid location for field %s\n", 364 ename); 365 return false; 366 } 367 if (NULL != lines) 368 { 369 size_t idx; 370 json_t *line; 371 372 json_array_foreach (lines, idx, line) 373 { 374 if (! json_is_string (line)) 375 { 376 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 377 "Invalid address line #%u in location\n", 378 (unsigned int) idx); 379 return false; 380 } 381 } 382 } 383 return true; 384 } 385 386 387 bool 388 TMH_products_array_valid (const json_t *products) 389 { 390 const json_t *product; 391 size_t idx; 392 bool valid = true; 393 394 if (! json_is_array (products)) 395 { 396 GNUNET_break_op (0); 397 return false; 398 } 399 json_array_foreach ((json_t *) products, idx, product) 400 { 401 /* FIXME: use TALER_MERCHANT_parse_product() instead? */ 402 const char *product_id = NULL; 403 const char *description; 404 uint64_t quantity = 0; 405 bool quantity_missing = true; 406 const char *unit_quantity = NULL; 407 bool unit_quantity_missing = true; 408 const char *unit = NULL; 409 struct TALER_Amount price = { .value = 0 }; 410 const char *image_data_url = NULL; 411 const json_t *taxes = NULL; 412 struct GNUNET_TIME_Timestamp delivery_date = { 0 }; 413 struct GNUNET_JSON_Specification spec[] = { 414 GNUNET_JSON_spec_mark_optional ( 415 TALER_JSON_spec_slug ("product_id", 416 &product_id), 417 NULL), 418 TALER_JSON_spec_i18n_str ("description", 419 &description), 420 GNUNET_JSON_spec_mark_optional ( 421 GNUNET_JSON_spec_uint64 ("quantity", 422 &quantity), 423 &quantity_missing), 424 GNUNET_JSON_spec_mark_optional ( 425 GNUNET_JSON_spec_string ("unit_quantity", 426 &unit_quantity), 427 &unit_quantity_missing), 428 GNUNET_JSON_spec_mark_optional ( 429 TALER_JSON_spec_slug ("unit", 430 &unit), 431 NULL), 432 GNUNET_JSON_spec_mark_optional ( 433 TALER_JSON_spec_amount_any ("price", 434 &price), 435 NULL), 436 GNUNET_JSON_spec_mark_optional ( 437 GNUNET_JSON_spec_string ("image", 438 &image_data_url), 439 NULL), 440 GNUNET_JSON_spec_mark_optional ( 441 GNUNET_JSON_spec_array_const ("taxes", 442 &taxes), 443 NULL), 444 GNUNET_JSON_spec_mark_optional ( 445 GNUNET_JSON_spec_timestamp ("delivery_date", 446 &delivery_date), 447 NULL), 448 GNUNET_JSON_spec_end () 449 }; 450 const char *ename; 451 unsigned int eline; 452 453 if (GNUNET_OK != 454 GNUNET_JSON_parse (product, 455 spec, 456 &ename, 457 &eline)) 458 { 459 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 460 "Invalid product #%u for field %s\n", 461 (unsigned int) idx, 462 ename); 463 return false; 464 } 465 if (quantity_missing) 466 { 467 if (unit_quantity_missing) 468 { 469 GNUNET_break_op (0); 470 valid = false; 471 } 472 } 473 if ( (NULL != image_data_url) && 474 (! TALER_MERCHANT_image_data_url_valid (image_data_url)) ) 475 { 476 GNUNET_break_op (0); 477 valid = false; 478 } 479 if ( (NULL != taxes) && 480 (! TALER_MERCHANT_taxes_array_valid (taxes)) ) 481 { 482 GNUNET_break_op (0); 483 valid = false; 484 } 485 GNUNET_JSON_parse_free (spec); 486 if (! valid) 487 break; 488 } 489 490 return valid; 491 } 492 493 494 enum GNUNET_GenericReturnValue 495 TMH_validate_unit_price_array (const struct TALER_Amount *prices, 496 size_t prices_len) 497 { 498 /* Check for duplicate currencies */ 499 for (size_t i = 0; i < prices_len; i++) 500 { 501 for (size_t j = i + 1; j < prices_len; j++) 502 { 503 if (GNUNET_YES == 504 TALER_amount_cmp_currency (&prices[i], 505 &prices[j])) 506 { 507 /* duplicate currency, not allowed! */ 508 GNUNET_break_op (0); 509 return GNUNET_SYSERR; 510 } 511 } 512 } 513 return GNUNET_OK; 514 } 515 516 517 bool 518 TMH_category_set_contains (const struct TMH_CategorySet *set, 519 uint64_t id) 520 { 521 for (size_t i = 0; i < set->len; i++) 522 if (set->ids[i] == id) 523 return true; 524 return false; 525 } 526 527 528 void 529 TMH_category_set_add (struct TMH_CategorySet *set, 530 uint64_t id) 531 { 532 if (TMH_category_set_contains (set, 533 id)) 534 return; 535 GNUNET_array_append (set->ids, 536 set->len, 537 id); 538 } 539 540 541 bool 542 TMH_unit_set_contains (const struct TMH_UnitSet *set, 543 const char *unit) 544 { 545 for (unsigned int i = 0; i < set->len; i++) 546 if (0 == strcmp (set->units[i], 547 unit)) 548 return true; 549 return false; 550 } 551 552 553 void 554 TMH_unit_set_add (struct TMH_UnitSet *set, 555 const char *unit) 556 { 557 if (TMH_unit_set_contains (set, 558 unit)) 559 return; 560 GNUNET_array_append (set->units, 561 set->len, 562 GNUNET_strdup (unit)); 563 } 564 565 566 void 567 TMH_unit_set_clear (struct TMH_UnitSet *set) 568 { 569 for (unsigned int i = 0; i < set->len; i++) 570 GNUNET_free (set->units[i]); 571 GNUNET_free (set->units); 572 set->units = NULL; 573 set->len = 0; 574 } 575 576 577 struct TMH_WireMethod * 578 TMH_setup_wire_account ( 579 struct TALER_FullPayto payto_uri, 580 const char *credit_facade_url, 581 const json_t *credit_facade_credentials) 582 { 583 struct TMH_WireMethod *wm; 584 char *emsg; 585 586 if (NULL != (emsg = TALER_payto_validate (payto_uri))) 587 { 588 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 589 "Invalid URI `%s': %s\n", 590 payto_uri.full_payto, 591 emsg); 592 GNUNET_free (emsg); 593 return NULL; 594 } 595 596 wm = GNUNET_new (struct TMH_WireMethod); 597 if (NULL != credit_facade_url) 598 wm->credit_facade_url 599 = GNUNET_strdup (credit_facade_url); 600 if (NULL != credit_facade_credentials) 601 wm->credit_facade_credentials 602 = json_incref ((json_t*) credit_facade_credentials); 603 GNUNET_CRYPTO_random_block (&wm->wire_salt, 604 sizeof (wm->wire_salt)); 605 wm->payto_uri.full_payto 606 = GNUNET_strdup (payto_uri.full_payto); 607 TALER_merchant_wire_signature_hash (payto_uri, 608 &wm->wire_salt, 609 &wm->h_wire); 610 wm->wire_method 611 = TALER_payto_get_method (payto_uri.full_payto); 612 wm->active = true; 613 return wm; 614 } 615 616 617 enum TALER_ErrorCode 618 TMH_check_token (const char *token, 619 const char *instance_id, 620 enum TMH_AuthScope *as) 621 { 622 enum TMH_AuthScope scope; 623 struct GNUNET_TIME_Timestamp expiration; 624 enum GNUNET_DB_QueryStatus qs; 625 struct TALER_MERCHANTDB_LoginTokenP btoken; 626 627 if (NULL == token) 628 { 629 *as = TMH_AS_NONE; 630 return TALER_EC_NONE; 631 } 632 if (0 != strncasecmp (token, 633 RFC_8959_PREFIX, 634 strlen (RFC_8959_PREFIX))) 635 { 636 *as = TMH_AS_NONE; 637 return TALER_EC_NONE; 638 } 639 token += strlen (RFC_8959_PREFIX); 640 if (GNUNET_OK != 641 GNUNET_STRINGS_string_to_data (token, 642 strlen (token), 643 &btoken, 644 sizeof (btoken))) 645 { 646 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 647 "Given authorization token `%s' is malformed\n", 648 token); 649 GNUNET_break_op (0); 650 return TALER_EC_GENERIC_TOKEN_MALFORMED; 651 } 652 qs = TALER_MERCHANTDB_get_login_token (TMH_db, 653 instance_id, 654 &btoken, 655 &expiration, 656 (uint32_t*) &scope); 657 if (qs < 0) 658 { 659 GNUNET_break (0); 660 return TALER_EC_GENERIC_DB_FETCH_FAILED; 661 } 662 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 663 { 664 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 665 "Authorization token `%s' unknown\n", 666 token); 667 return TALER_EC_GENERIC_TOKEN_UNKNOWN; 668 } 669 if (GNUNET_TIME_absolute_is_past (expiration.abs_time)) 670 { 671 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 672 "Authorization token `%s' expired\n", 673 token); 674 return TALER_EC_GENERIC_TOKEN_EXPIRED; 675 } 676 *as = scope; 677 return TALER_EC_NONE; 678 } 679 680 681 enum GNUNET_GenericReturnValue 682 TMH_check_auth_config (struct MHD_Connection *connection, 683 const json_t *jauth, 684 const char **auth_password) 685 { 686 bool auth_wellformed = false; 687 const char *auth_method = json_string_value (json_object_get (jauth, 688 "method")); 689 690 *auth_password = NULL; 691 if (NULL == auth_method) 692 { 693 GNUNET_break_op (0); 694 } 695 else if ((GNUNET_YES != TMH_strict_v19) && 696 (0 == strcmp (auth_method, 697 "external"))) 698 { 699 auth_wellformed = true; 700 } 701 else if (GNUNET_YES == TMH_auth_disabled) 702 { 703 auth_wellformed = true; 704 } 705 else if (0 == strcmp (auth_method, 706 "token")) 707 { 708 json_t *pw_value; 709 710 pw_value = json_object_get (jauth, 711 "password"); 712 if (NULL == pw_value) 713 { 714 pw_value = json_object_get (jauth, 715 "token"); 716 } 717 if (NULL == pw_value) 718 { 719 auth_wellformed = false; 720 GNUNET_break_op (0); 721 } 722 else 723 { 724 *auth_password = json_string_value (pw_value); 725 if (NULL != *auth_password) 726 { 727 if (0 == strncasecmp (RFC_8959_PREFIX, 728 *auth_password, 729 strlen (RFC_8959_PREFIX))) 730 { 731 *auth_password = *auth_password + strlen (RFC_8959_PREFIX); 732 } 733 auth_wellformed = true; 734 } 735 } 736 } 737 738 if (! auth_wellformed) 739 { 740 GNUNET_break_op (0); 741 return (MHD_YES == 742 TALER_MHD_reply_with_error (connection, 743 MHD_HTTP_BAD_REQUEST, 744 TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH, 745 "bad authentication config")) 746 ? GNUNET_NO 747 : GNUNET_SYSERR; 748 } 749 return GNUNET_OK; 750 } 751 752 753 void 754 TMH_uuid_from_string (const char *uuids, 755 struct GNUNET_Uuid *uuid) 756 { 757 struct GNUNET_HashCode hc; 758 759 GNUNET_static_assert (sizeof (hc) > sizeof (*uuid)); 760 GNUNET_CRYPTO_hash (uuids, 761 strlen (uuids), 762 &hc); 763 GNUNET_memcpy (uuid, 764 &hc, 765 sizeof (*uuid)); 766 } 767 768 769 /** 770 * Closure for #trigger_webhook_cb. 771 * 772 * @param instance which is the instance we work with 773 * @param root JSON data to fill into the template 774 * @param rv, query of the TALER_TEMPLATEING_fill 775 */ 776 struct Trigger 777 { 778 const char *instance; 779 780 const json_t *root; 781 782 enum GNUNET_DB_QueryStatus rv; 783 784 }; 785 786 /** 787 * Typically called by `TMH_trigger_webhook`. 788 * 789 * @param[in,out] cls a `struct Trigger` with information about the webhook 790 * @param webhook_serial reference to the configured webhook template. 791 * @param event_type is the event/action of the webhook 792 * @param url to make request to 793 * @param http_method use for the webhook 794 * @param header_template of the webhook 795 * @param body_template of the webhook 796 */ 797 static void 798 trigger_webhook_cb (void *cls, 799 uint64_t webhook_serial, 800 const char *event_type, 801 const char *url, 802 const char *http_method, 803 const char *header_template, 804 const char *body_template) 805 { 806 struct Trigger *t = cls; 807 void *header = NULL; 808 void *body = NULL; 809 size_t header_size; 810 size_t body_size; 811 812 if (NULL != header_template) 813 { 814 int ret; 815 816 ret = TALER_TEMPLATING_fill (header_template, 817 t->root, 818 &header, 819 &header_size); 820 if (0 != ret) 821 { 822 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 823 "Failed to expand webhook header template for webhook %llu (%d)\n", 824 (unsigned long long) webhook_serial, 825 ret); 826 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 827 return; 828 } 829 /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */ 830 GNUNET_assert ('\0' == ((const char *) header)[header_size]); 831 } 832 if (NULL != body_template) 833 { 834 int ret; 835 836 ret = TALER_TEMPLATING_fill (body_template, 837 t->root, 838 &body, 839 &body_size); 840 if (0 != ret) 841 { 842 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 843 "Failed to expand webhook body template for webhook %llu (%d)\n", 844 (unsigned long long) webhook_serial, 845 ret); 846 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 847 return; 848 } 849 /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */ 850 GNUNET_assert ('\0' == ((const char *) body)[body_size]); 851 } 852 t->rv = TALER_MERCHANTDB_insert_pending_webhook (TMH_db, 853 t->instance, 854 webhook_serial, 855 url, 856 http_method, 857 header, 858 body); 859 if (t->rv > 0) 860 { 861 struct GNUNET_DB_EventHeaderP es = { 862 .size = htons (sizeof(es)), 863 .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING) 864 }; 865 const void *extra = NULL; 866 size_t extra_size = 0; 867 TALER_MERCHANTDB_event_notify (TMH_db, 868 &es, 869 &extra, 870 extra_size); 871 } 872 /* allocated by mustach, hence use free(), not GNUNET_free() */ 873 free (header); 874 free (body); 875 } 876 877 878 /** 879 * TMH_trigger_webhook is a function that need to be use when someone 880 * pay. Merchant need to have a notification. 881 * 882 * @param instance that we need to send the webhook as a notification 883 * @param event of the webhook 884 * @param args argument of the function 885 */ 886 enum GNUNET_DB_QueryStatus 887 TMH_trigger_webhook (const char *instance, 888 const char *event, 889 const json_t *args) 890 { 891 struct Trigger t = { 892 .instance = instance, 893 .root = args 894 }; 895 enum GNUNET_DB_QueryStatus qs; 896 897 qs = TALER_MERCHANTDB_iterate_webhooks_by_event (TMH_db, 898 instance, 899 event, 900 &trigger_webhook_cb, 901 &t); 902 if (qs < 0) 903 return qs; 904 return t.rv; 905 } 906 907 908 enum GNUNET_GenericReturnValue 909 TMH_base_url_by_connection (struct MHD_Connection *connection, 910 const char *instance, 911 struct GNUNET_Buffer *buf) 912 { 913 const char *host; 914 const char *forwarded_host; 915 const char *forwarded_port; 916 const char *uri_path; 917 918 memset (buf, 919 0, 920 sizeof (*buf)); 921 if (NULL != TMH_base_url) 922 { 923 GNUNET_buffer_write_str (buf, 924 TMH_base_url); 925 } 926 else 927 { 928 if (GNUNET_YES == 929 TALER_mhd_is_https (connection)) 930 GNUNET_buffer_write_str (buf, 931 "https://"); 932 else 933 GNUNET_buffer_write_str (buf, 934 "http://"); 935 host = MHD_lookup_connection_value (connection, 936 MHD_HEADER_KIND, 937 MHD_HTTP_HEADER_HOST); 938 forwarded_host = MHD_lookup_connection_value (connection, 939 MHD_HEADER_KIND, 940 "X-Forwarded-Host"); 941 if (NULL != forwarded_host) 942 { 943 GNUNET_buffer_write_str (buf, 944 forwarded_host); 945 } 946 else 947 { 948 if (NULL == host) 949 { 950 GNUNET_buffer_clear (buf); 951 GNUNET_break (0); 952 return GNUNET_SYSERR; 953 } 954 GNUNET_buffer_write_str (buf, 955 host); 956 } 957 forwarded_port = MHD_lookup_connection_value (connection, 958 MHD_HEADER_KIND, 959 "X-Forwarded-Port"); 960 if (NULL != forwarded_port) 961 { 962 GNUNET_buffer_write_str (buf, 963 ":"); 964 GNUNET_buffer_write_str (buf, 965 forwarded_port); 966 } 967 uri_path = MHD_lookup_connection_value (connection, 968 MHD_HEADER_KIND, 969 "X-Forwarded-Prefix"); 970 if (NULL != uri_path) 971 GNUNET_buffer_write_path (buf, 972 uri_path); 973 } 974 if (0 != strcmp (instance, 975 "admin")) 976 { 977 GNUNET_buffer_write_path (buf, 978 "/instances/"); 979 GNUNET_buffer_write_str (buf, 980 instance); 981 } 982 return GNUNET_OK; 983 } 984 985 986 enum GNUNET_GenericReturnValue 987 TMH_taler_uri_by_connection (struct MHD_Connection *connection, 988 const char *method, 989 const char *instance, 990 struct GNUNET_Buffer *buf) 991 { 992 const char *host; 993 const char *forwarded_host; 994 const char *forwarded_port; 995 const char *uri_path; 996 997 memset (buf, 998 0, 999 sizeof (*buf)); 1000 host = MHD_lookup_connection_value (connection, 1001 MHD_HEADER_KIND, 1002 "Host"); 1003 forwarded_host = MHD_lookup_connection_value (connection, 1004 MHD_HEADER_KIND, 1005 "X-Forwarded-Host"); 1006 forwarded_port = MHD_lookup_connection_value (connection, 1007 MHD_HEADER_KIND, 1008 "X-Forwarded-Port"); 1009 uri_path = MHD_lookup_connection_value (connection, 1010 MHD_HEADER_KIND, 1011 "X-Forwarded-Prefix"); 1012 if (NULL != forwarded_host) 1013 host = forwarded_host; 1014 if (NULL == host) 1015 { 1016 GNUNET_break (0); 1017 return GNUNET_SYSERR; 1018 } 1019 GNUNET_buffer_write_str (buf, 1020 "taler"); 1021 if (GNUNET_NO == TALER_mhd_is_https (connection)) 1022 GNUNET_buffer_write_str (buf, 1023 "+http"); 1024 GNUNET_buffer_write_str (buf, 1025 "://"); 1026 GNUNET_buffer_write_str (buf, 1027 method); 1028 GNUNET_buffer_write_str (buf, 1029 "/"); 1030 GNUNET_buffer_write_str (buf, 1031 host); 1032 if (NULL != forwarded_port) 1033 { 1034 GNUNET_buffer_write_str (buf, 1035 ":"); 1036 GNUNET_buffer_write_str (buf, 1037 forwarded_port); 1038 } 1039 if (NULL != uri_path) 1040 GNUNET_buffer_write_path (buf, 1041 uri_path); 1042 if (0 != strcmp ("admin", 1043 instance)) 1044 { 1045 GNUNET_buffer_write_path (buf, 1046 "instances"); 1047 GNUNET_buffer_write_path (buf, 1048 instance); 1049 } 1050 return GNUNET_OK; 1051 } 1052 1053 1054 /** 1055 * Closure for #add_matching_account(). 1056 */ 1057 struct ExchangeMatchContext 1058 { 1059 /** 1060 * Wire method to match, NULL for all. 1061 */ 1062 const char *wire_method; 1063 1064 /** 1065 * Array of accounts to return. 1066 */ 1067 json_t *accounts; 1068 }; 1069 1070 1071 /** 1072 * If the given account is feasible, add it to the array 1073 * of accounts we return. 1074 * 1075 * @param cls a `struct PostReserveContext` 1076 * @param payto_uri URI of the account 1077 * @param conversion_url URL of a conversion service 1078 * @param debit_restrictions restrictions for debits from account 1079 * @param credit_restrictions restrictions for credits to account 1080 * @param master_sig signature affirming the account 1081 */ 1082 static void 1083 add_matching_account ( 1084 void *cls, 1085 struct TALER_FullPayto payto_uri, 1086 const char *conversion_url, 1087 const json_t *debit_restrictions, 1088 const json_t *credit_restrictions, 1089 const struct TALER_MasterSignatureP *master_sig) 1090 { 1091 struct ExchangeMatchContext *rc = cls; 1092 char *method; 1093 1094 method = TALER_payto_get_method (payto_uri.full_payto); 1095 if ( (NULL == rc->wire_method) || 1096 (0 == strcmp (method, 1097 rc->wire_method)) ) 1098 { 1099 json_t *acc; 1100 1101 acc = GNUNET_JSON_PACK ( 1102 TALER_JSON_pack_full_payto ("payto_uri", 1103 payto_uri), 1104 GNUNET_JSON_pack_data_auto ("master_sig", 1105 master_sig), 1106 GNUNET_JSON_pack_allow_null ( 1107 GNUNET_JSON_pack_string ("conversion_url", 1108 conversion_url)), 1109 GNUNET_JSON_pack_array_incref ("credit_restrictions", 1110 (json_t *) credit_restrictions), 1111 GNUNET_JSON_pack_array_incref ("debit_restrictions", 1112 (json_t *) debit_restrictions) 1113 ); 1114 GNUNET_assert (0 == 1115 json_array_append_new (rc->accounts, 1116 acc)); 1117 } 1118 GNUNET_free (method); 1119 } 1120 1121 1122 /** 1123 * Return JSON array with all of the exchange accounts 1124 * that support the given @a wire_method. 1125 * 1126 * @param master_pub master public key to match exchange by 1127 * @param wire_method NULL for any 1128 * @return JSON array with information about all matching accounts 1129 */ 1130 json_t * 1131 TMH_exchange_accounts_by_method ( 1132 const struct TALER_MasterPublicKeyP *master_pub, 1133 const char *wire_method) 1134 { 1135 struct ExchangeMatchContext emc = { 1136 .wire_method = wire_method, 1137 .accounts = json_array () 1138 }; 1139 enum GNUNET_DB_QueryStatus qs; 1140 1141 GNUNET_assert (NULL != emc.accounts); 1142 qs = TALER_MERCHANTDB_iterate_exchange_accounts (TMH_db, 1143 master_pub, 1144 &add_matching_account, 1145 &emc); 1146 if (qs < 0) 1147 { 1148 json_decref (emc.accounts); 1149 return NULL; 1150 } 1151 return emc.accounts; 1152 } 1153 1154 1155 char * 1156 TMH_make_order_status_url (struct MHD_Connection *con, 1157 const char *order_id, 1158 const char *session_id, 1159 const char *instance_id, 1160 struct TALER_ClaimTokenP *claim_token, 1161 struct TALER_PrivateContractHashP *h_contract) 1162 { 1163 struct GNUNET_Buffer buf; 1164 /* Number of query parameters written so far */ 1165 unsigned int num_qp = 0; 1166 1167 GNUNET_assert (NULL != instance_id); 1168 GNUNET_assert (NULL != order_id); 1169 if (GNUNET_OK != 1170 TMH_base_url_by_connection (con, 1171 instance_id, 1172 &buf)) 1173 { 1174 GNUNET_break (0); 1175 return NULL; 1176 } 1177 GNUNET_buffer_write_path (&buf, 1178 "/orders"); 1179 GNUNET_buffer_write_path (&buf, 1180 order_id); 1181 if ( (NULL != claim_token) && 1182 (! GNUNET_is_zero (claim_token)) ) 1183 { 1184 /* 'token=' for human readability */ 1185 GNUNET_buffer_write_str (&buf, 1186 "?token="); 1187 GNUNET_buffer_write_data_encoded (&buf, 1188 (char *) claim_token, 1189 sizeof (*claim_token)); 1190 num_qp++; 1191 } 1192 1193 if (NULL != session_id) 1194 { 1195 if (num_qp > 0) 1196 GNUNET_buffer_write_str (&buf, 1197 "&session_id="); 1198 else 1199 GNUNET_buffer_write_str (&buf, 1200 "?session_id="); 1201 GNUNET_buffer_write_str (&buf, 1202 session_id); 1203 num_qp++; 1204 } 1205 1206 if (NULL != h_contract) 1207 { 1208 if (num_qp > 0) 1209 GNUNET_buffer_write_str (&buf, 1210 "&h_contract="); 1211 else 1212 GNUNET_buffer_write_str (&buf, 1213 "?h_contract="); 1214 GNUNET_buffer_write_data_encoded (&buf, 1215 (char *) h_contract, 1216 sizeof (*h_contract)); 1217 } 1218 1219 return GNUNET_buffer_reap_str (&buf); 1220 } 1221 1222 1223 char * 1224 TMH_make_taler_pay_uri (struct MHD_Connection *con, 1225 const char *order_id, 1226 const char *session_id, 1227 const char *instance_id, 1228 struct TALER_ClaimTokenP *claim_token) 1229 { 1230 struct GNUNET_Buffer buf; 1231 1232 GNUNET_assert (NULL != instance_id); 1233 GNUNET_assert (NULL != order_id); 1234 if (GNUNET_OK != 1235 TMH_taler_uri_by_connection (con, 1236 "pay", 1237 instance_id, 1238 &buf)) 1239 { 1240 GNUNET_break (0); 1241 return NULL; 1242 } 1243 GNUNET_buffer_write_path (&buf, 1244 order_id); 1245 GNUNET_buffer_write_path (&buf, 1246 (NULL == session_id) 1247 ? "" 1248 : session_id); 1249 if ( (NULL != claim_token) && 1250 (! GNUNET_is_zero (claim_token))) 1251 { 1252 /* Just 'c=' because this goes into QR 1253 codes, so this is more compact. */ 1254 GNUNET_buffer_write_str (&buf, 1255 "?c="); 1256 GNUNET_buffer_write_data_encoded (&buf, 1257 (char *) claim_token, 1258 sizeof (struct TALER_ClaimTokenP)); 1259 } 1260 1261 return GNUNET_buffer_reap_str (&buf); 1262 } 1263 1264 1265 enum GNUNET_GenericReturnValue 1266 TMH_compute_order_total (const json_t *contract_terms, 1267 int16_t choice_index, 1268 struct TALER_Amount *total) 1269 { 1270 enum TALER_MERCHANT_ContractVersion version 1271 = TALER_MERCHANT_CONTRACT_VERSION_0; 1272 const json_t *amount_bearer = contract_terms; 1273 struct TALER_Amount amount; 1274 const json_t *amount_external; 1275 struct GNUNET_JSON_Specification spec[] = { 1276 GNUNET_JSON_spec_mark_optional ( 1277 TALER_MERCHANT_spec_contract_version ("version", 1278 &version), 1279 NULL), 1280 GNUNET_JSON_spec_end () 1281 }; 1282 1283 if (GNUNET_OK != 1284 GNUNET_JSON_parse (contract_terms, 1285 spec, 1286 NULL, 1287 NULL)) 1288 { 1289 GNUNET_break (0); 1290 return GNUNET_SYSERR; 1291 } 1292 if (TALER_MERCHANT_CONTRACT_VERSION_1 == version) 1293 { 1294 if (0 > choice_index) 1295 { 1296 /* Without a selected choice the total is undefined; the caller 1297 must only ask for orders where a choice was picked. */ 1298 GNUNET_break (0); 1299 return GNUNET_SYSERR; 1300 } 1301 amount_bearer = json_array_get ( 1302 json_object_get (contract_terms, 1303 "choices"), 1304 (size_t) choice_index); 1305 if (NULL == amount_bearer) 1306 { 1307 GNUNET_break (0); 1308 return GNUNET_SYSERR; 1309 } 1310 } 1311 { 1312 struct GNUNET_JSON_Specification aspec[] = { 1313 TALER_JSON_spec_amount_any ("amount", 1314 &amount), 1315 GNUNET_JSON_spec_end () 1316 }; 1317 1318 if (GNUNET_OK != 1319 GNUNET_JSON_parse (amount_bearer, 1320 aspec, 1321 NULL, 1322 NULL)) 1323 { 1324 GNUNET_break (0); 1325 return GNUNET_SYSERR; 1326 } 1327 } 1328 *total = amount; 1329 amount_external = json_object_get (contract_terms, 1330 "amount_external"); 1331 { 1332 size_t idx; 1333 json_t *entry; 1334 1335 json_array_foreach ((json_t *) amount_external, idx, entry) 1336 { 1337 struct TALER_Amount ext_amount; 1338 struct GNUNET_JSON_Specification espec[] = { 1339 TALER_JSON_spec_amount_any ("amount", 1340 &ext_amount), 1341 GNUNET_JSON_spec_end () 1342 }; 1343 1344 if (GNUNET_OK != 1345 GNUNET_JSON_parse (entry, 1346 espec, 1347 NULL, 1348 NULL)) 1349 { 1350 GNUNET_break (0); 1351 return GNUNET_SYSERR; 1352 } 1353 if (0 > 1354 TALER_amount_add (total, 1355 total, 1356 &ext_amount)) 1357 { 1358 GNUNET_break (0); 1359 return GNUNET_SYSERR; 1360 } 1361 } 1362 } 1363 return GNUNET_OK; 1364 }