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