taler-merchant-httpd_helper.c (36461B)
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 (GNUNET_CRYPTO_QUALITY_NONCE, 587 &wm->wire_salt, 588 sizeof (wm->wire_salt)); 589 wm->payto_uri.full_payto 590 = GNUNET_strdup (payto_uri.full_payto); 591 TALER_merchant_wire_signature_hash (payto_uri, 592 &wm->wire_salt, 593 &wm->h_wire); 594 wm->wire_method 595 = TALER_payto_get_method (payto_uri.full_payto); 596 wm->active = true; 597 return wm; 598 } 599 600 601 enum TALER_ErrorCode 602 TMH_check_token (const char *token, 603 const char *instance_id, 604 enum TMH_AuthScope *as) 605 { 606 enum TMH_AuthScope scope; 607 struct GNUNET_TIME_Timestamp expiration; 608 enum GNUNET_DB_QueryStatus qs; 609 struct TALER_MERCHANTDB_LoginTokenP btoken; 610 611 if (NULL == token) 612 { 613 *as = TMH_AS_NONE; 614 return TALER_EC_NONE; 615 } 616 if (0 != strncasecmp (token, 617 RFC_8959_PREFIX, 618 strlen (RFC_8959_PREFIX))) 619 { 620 *as = TMH_AS_NONE; 621 return TALER_EC_NONE; 622 } 623 token += strlen (RFC_8959_PREFIX); 624 if (GNUNET_OK != 625 GNUNET_STRINGS_string_to_data (token, 626 strlen (token), 627 &btoken, 628 sizeof (btoken))) 629 { 630 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 631 "Given authorization token `%s' is malformed\n", 632 token); 633 GNUNET_break_op (0); 634 return TALER_EC_GENERIC_TOKEN_MALFORMED; 635 } 636 qs = TALER_MERCHANTDB_select_login_token (TMH_db, 637 instance_id, 638 &btoken, 639 &expiration, 640 (uint32_t*) &scope); 641 if (qs < 0) 642 { 643 GNUNET_break (0); 644 return TALER_EC_GENERIC_DB_FETCH_FAILED; 645 } 646 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 647 { 648 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 649 "Authorization token `%s' unknown\n", 650 token); 651 return TALER_EC_GENERIC_TOKEN_UNKNOWN; 652 } 653 if (GNUNET_TIME_absolute_is_past (expiration.abs_time)) 654 { 655 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 656 "Authorization token `%s' expired\n", 657 token); 658 return TALER_EC_GENERIC_TOKEN_EXPIRED; 659 } 660 *as = scope; 661 return TALER_EC_NONE; 662 } 663 664 665 enum GNUNET_GenericReturnValue 666 TMH_check_auth_config (struct MHD_Connection *connection, 667 const json_t *jauth, 668 const char **auth_password) 669 { 670 bool auth_wellformed = false; 671 const char *auth_method = json_string_value (json_object_get (jauth, 672 "method")); 673 674 *auth_password = NULL; 675 if (NULL == auth_method) 676 { 677 GNUNET_break_op (0); 678 } 679 else if ((GNUNET_YES != TMH_strict_v19) && 680 (0 == strcmp (auth_method, 681 "external"))) 682 { 683 auth_wellformed = true; 684 } 685 else if (GNUNET_YES == TMH_auth_disabled) 686 { 687 auth_wellformed = true; 688 } 689 else if (0 == strcmp (auth_method, 690 "token")) 691 { 692 json_t *pw_value; 693 694 pw_value = json_object_get (jauth, 695 "password"); 696 if (NULL == pw_value) 697 { 698 pw_value = json_object_get (jauth, 699 "token"); 700 } 701 if (NULL == pw_value) 702 { 703 auth_wellformed = false; 704 GNUNET_break_op (0); 705 } 706 else 707 { 708 *auth_password = json_string_value (pw_value); 709 if (NULL != *auth_password) 710 { 711 if (0 == strncasecmp (RFC_8959_PREFIX, 712 *auth_password, 713 strlen (RFC_8959_PREFIX))) 714 { 715 *auth_password = *auth_password + strlen (RFC_8959_PREFIX); 716 } 717 auth_wellformed = true; 718 } 719 } 720 } 721 722 if (! auth_wellformed) 723 { 724 GNUNET_break_op (0); 725 return (MHD_YES == 726 TALER_MHD_reply_with_error (connection, 727 MHD_HTTP_BAD_REQUEST, 728 TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH, 729 "bad authentication config")) 730 ? GNUNET_NO 731 : GNUNET_SYSERR; 732 } 733 return GNUNET_OK; 734 } 735 736 737 void 738 TMH_uuid_from_string (const char *uuids, 739 struct GNUNET_Uuid *uuid) 740 { 741 struct GNUNET_HashCode hc; 742 743 GNUNET_CRYPTO_hash (uuids, 744 strlen (uuids), 745 &hc); 746 GNUNET_static_assert (sizeof (hc) > sizeof (*uuid)); 747 GNUNET_memcpy (uuid, 748 &hc, 749 sizeof (*uuid)); 750 } 751 752 753 /** 754 * Closure for #trigger_webhook_cb. 755 * 756 * @param instance which is the instance we work with 757 * @param root JSON data to fill into the template 758 * @param rv, query of the TALER_TEMPLATEING_fill 759 */ 760 struct Trigger 761 { 762 const char *instance; 763 764 const json_t *root; 765 766 enum GNUNET_DB_QueryStatus rv; 767 768 }; 769 770 /** 771 * Typically called by `TMH_trigger_webhook`. 772 * 773 * @param[in,out] cls a `struct Trigger` with information about the webhook 774 * @param webhook_serial reference to the configured webhook template. 775 * @param event_type is the event/action of the webhook 776 * @param url to make request to 777 * @param http_method use for the webhook 778 * @param header_template of the webhook 779 * @param body_template of the webhook 780 */ 781 static void 782 trigger_webhook_cb (void *cls, 783 uint64_t webhook_serial, 784 const char *event_type, 785 const char *url, 786 const char *http_method, 787 const char *header_template, 788 const char *body_template) 789 { 790 struct Trigger *t = cls; 791 void *header = NULL; 792 void *body = NULL; 793 size_t header_size; 794 size_t body_size; 795 796 if (NULL != header_template) 797 { 798 int ret; 799 800 ret = TALER_TEMPLATING_fill (header_template, 801 t->root, 802 &header, 803 &header_size); 804 if (0 != ret) 805 { 806 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 807 "Failed to expand webhook header template for webhook %llu (%d)\n", 808 (unsigned long long) webhook_serial, 809 ret); 810 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 811 return; 812 } 813 /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */ 814 GNUNET_assert ('\0' == ((const char *) header)[header_size]); 815 } 816 if (NULL != body_template) 817 { 818 int ret; 819 820 ret = TALER_TEMPLATING_fill (body_template, 821 t->root, 822 &body, 823 &body_size); 824 if (0 != ret) 825 { 826 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 827 "Failed to expand webhook body template for webhook %llu (%d)\n", 828 (unsigned long long) webhook_serial, 829 ret); 830 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 831 return; 832 } 833 /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */ 834 GNUNET_assert ('\0' == ((const char *) body)[body_size]); 835 } 836 t->rv = TALER_MERCHANTDB_insert_pending_webhook (TMH_db, 837 t->instance, 838 webhook_serial, 839 url, 840 http_method, 841 header, 842 body); 843 if (t->rv > 0) 844 { 845 struct GNUNET_DB_EventHeaderP es = { 846 .size = htons (sizeof(es)), 847 .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING) 848 }; 849 const void *extra = NULL; 850 size_t extra_size = 0; 851 TALER_MERCHANTDB_event_notify (TMH_db, 852 &es, 853 &extra, 854 extra_size); 855 } 856 /* allocated by mustach, hence use free(), not GNUNET_free() */ 857 free (header); 858 free (body); 859 } 860 861 862 /** 863 * TMH_trigger_webhook is a function that need to be use when someone 864 * pay. Merchant need to have a notification. 865 * 866 * @param instance that we need to send the webhook as a notification 867 * @param event of the webhook 868 * @param args argument of the function 869 */ 870 enum GNUNET_DB_QueryStatus 871 TMH_trigger_webhook (const char *instance, 872 const char *event, 873 const json_t *args) 874 { 875 struct Trigger t = { 876 .instance = instance, 877 .root = args 878 }; 879 enum GNUNET_DB_QueryStatus qs; 880 881 qs = TALER_MERCHANTDB_lookup_webhook_by_event (TMH_db, 882 instance, 883 event, 884 &trigger_webhook_cb, 885 &t); 886 if (qs < 0) 887 return qs; 888 return t.rv; 889 } 890 891 892 enum GNUNET_GenericReturnValue 893 TMH_base_url_by_connection (struct MHD_Connection *connection, 894 const char *instance, 895 struct GNUNET_Buffer *buf) 896 { 897 const char *host; 898 const char *forwarded_host; 899 const char *forwarded_port; 900 const char *uri_path; 901 902 memset (buf, 903 0, 904 sizeof (*buf)); 905 if (NULL != TMH_base_url) 906 { 907 GNUNET_buffer_write_str (buf, 908 TMH_base_url); 909 } 910 else 911 { 912 if (GNUNET_YES == 913 TALER_mhd_is_https (connection)) 914 GNUNET_buffer_write_str (buf, 915 "https://"); 916 else 917 GNUNET_buffer_write_str (buf, 918 "http://"); 919 host = MHD_lookup_connection_value (connection, 920 MHD_HEADER_KIND, 921 MHD_HTTP_HEADER_HOST); 922 forwarded_host = MHD_lookup_connection_value (connection, 923 MHD_HEADER_KIND, 924 "X-Forwarded-Host"); 925 if (NULL != forwarded_host) 926 { 927 GNUNET_buffer_write_str (buf, 928 forwarded_host); 929 } 930 else 931 { 932 if (NULL == host) 933 { 934 GNUNET_buffer_clear (buf); 935 GNUNET_break (0); 936 return GNUNET_SYSERR; 937 } 938 GNUNET_buffer_write_str (buf, 939 host); 940 } 941 forwarded_port = MHD_lookup_connection_value (connection, 942 MHD_HEADER_KIND, 943 "X-Forwarded-Port"); 944 if (NULL != forwarded_port) 945 { 946 GNUNET_buffer_write_str (buf, 947 ":"); 948 GNUNET_buffer_write_str (buf, 949 forwarded_port); 950 } 951 uri_path = MHD_lookup_connection_value (connection, 952 MHD_HEADER_KIND, 953 "X-Forwarded-Prefix"); 954 if (NULL != uri_path) 955 GNUNET_buffer_write_path (buf, 956 uri_path); 957 } 958 if (0 != strcmp (instance, 959 "admin")) 960 { 961 GNUNET_buffer_write_path (buf, 962 "/instances/"); 963 GNUNET_buffer_write_str (buf, 964 instance); 965 } 966 return GNUNET_OK; 967 } 968 969 970 enum GNUNET_GenericReturnValue 971 TMH_taler_uri_by_connection (struct MHD_Connection *connection, 972 const char *method, 973 const char *instance, 974 struct GNUNET_Buffer *buf) 975 { 976 const char *host; 977 const char *forwarded_host; 978 const char *forwarded_port; 979 const char *uri_path; 980 981 memset (buf, 982 0, 983 sizeof (*buf)); 984 host = MHD_lookup_connection_value (connection, 985 MHD_HEADER_KIND, 986 "Host"); 987 forwarded_host = MHD_lookup_connection_value (connection, 988 MHD_HEADER_KIND, 989 "X-Forwarded-Host"); 990 forwarded_port = MHD_lookup_connection_value (connection, 991 MHD_HEADER_KIND, 992 "X-Forwarded-Port"); 993 uri_path = MHD_lookup_connection_value (connection, 994 MHD_HEADER_KIND, 995 "X-Forwarded-Prefix"); 996 if (NULL != forwarded_host) 997 host = forwarded_host; 998 if (NULL == host) 999 { 1000 GNUNET_break (0); 1001 return GNUNET_SYSERR; 1002 } 1003 GNUNET_buffer_write_str (buf, 1004 "taler"); 1005 if (GNUNET_NO == TALER_mhd_is_https (connection)) 1006 GNUNET_buffer_write_str (buf, 1007 "+http"); 1008 GNUNET_buffer_write_str (buf, 1009 "://"); 1010 GNUNET_buffer_write_str (buf, 1011 method); 1012 GNUNET_buffer_write_str (buf, 1013 "/"); 1014 GNUNET_buffer_write_str (buf, 1015 host); 1016 if (NULL != forwarded_port) 1017 { 1018 GNUNET_buffer_write_str (buf, 1019 ":"); 1020 GNUNET_buffer_write_str (buf, 1021 forwarded_port); 1022 } 1023 if (NULL != uri_path) 1024 GNUNET_buffer_write_path (buf, 1025 uri_path); 1026 if (0 != strcmp ("admin", 1027 instance)) 1028 { 1029 GNUNET_buffer_write_path (buf, 1030 "instances"); 1031 GNUNET_buffer_write_path (buf, 1032 instance); 1033 } 1034 return GNUNET_OK; 1035 } 1036 1037 1038 /** 1039 * Closure for #add_matching_account(). 1040 */ 1041 struct ExchangeMatchContext 1042 { 1043 /** 1044 * Wire method to match, NULL for all. 1045 */ 1046 const char *wire_method; 1047 1048 /** 1049 * Array of accounts to return. 1050 */ 1051 json_t *accounts; 1052 }; 1053 1054 1055 /** 1056 * If the given account is feasible, add it to the array 1057 * of accounts we return. 1058 * 1059 * @param cls a `struct PostReserveContext` 1060 * @param payto_uri URI of the account 1061 * @param conversion_url URL of a conversion service 1062 * @param debit_restrictions restrictions for debits from account 1063 * @param credit_restrictions restrictions for credits to account 1064 * @param master_sig signature affirming the account 1065 */ 1066 static void 1067 add_matching_account ( 1068 void *cls, 1069 struct TALER_FullPayto payto_uri, 1070 const char *conversion_url, 1071 const json_t *debit_restrictions, 1072 const json_t *credit_restrictions, 1073 const struct TALER_MasterSignatureP *master_sig) 1074 { 1075 struct ExchangeMatchContext *rc = cls; 1076 char *method; 1077 1078 method = TALER_payto_get_method (payto_uri.full_payto); 1079 if ( (NULL == rc->wire_method) || 1080 (0 == strcmp (method, 1081 rc->wire_method)) ) 1082 { 1083 json_t *acc; 1084 1085 acc = GNUNET_JSON_PACK ( 1086 TALER_JSON_pack_full_payto ("payto_uri", 1087 payto_uri), 1088 GNUNET_JSON_pack_data_auto ("master_sig", 1089 master_sig), 1090 GNUNET_JSON_pack_allow_null ( 1091 GNUNET_JSON_pack_string ("conversion_url", 1092 conversion_url)), 1093 GNUNET_JSON_pack_array_incref ("credit_restrictions", 1094 (json_t *) credit_restrictions), 1095 GNUNET_JSON_pack_array_incref ("debit_restrictions", 1096 (json_t *) debit_restrictions) 1097 ); 1098 GNUNET_assert (0 == 1099 json_array_append_new (rc->accounts, 1100 acc)); 1101 } 1102 GNUNET_free (method); 1103 } 1104 1105 1106 /** 1107 * Return JSON array with all of the exchange accounts 1108 * that support the given @a wire_method. 1109 * 1110 * @param master_pub master public key to match exchange by 1111 * @param wire_method NULL for any 1112 * @return JSON array with information about all matching accounts 1113 */ 1114 json_t * 1115 TMH_exchange_accounts_by_method ( 1116 const struct TALER_MasterPublicKeyP *master_pub, 1117 const char *wire_method) 1118 { 1119 struct ExchangeMatchContext emc = { 1120 .wire_method = wire_method, 1121 .accounts = json_array () 1122 }; 1123 enum GNUNET_DB_QueryStatus qs; 1124 1125 GNUNET_assert (NULL != emc.accounts); 1126 qs = TALER_MERCHANTDB_select_accounts_by_exchange (TMH_db, 1127 master_pub, 1128 &add_matching_account, 1129 &emc); 1130 if (qs < 0) 1131 { 1132 json_decref (emc.accounts); 1133 return NULL; 1134 } 1135 return emc.accounts; 1136 } 1137 1138 1139 char * 1140 TMH_make_order_status_url (struct MHD_Connection *con, 1141 const char *order_id, 1142 const char *session_id, 1143 const char *instance_id, 1144 struct TALER_ClaimTokenP *claim_token, 1145 struct TALER_PrivateContractHashP *h_contract) 1146 { 1147 struct GNUNET_Buffer buf; 1148 /* Number of query parameters written so far */ 1149 unsigned int num_qp = 0; 1150 1151 GNUNET_assert (NULL != instance_id); 1152 GNUNET_assert (NULL != order_id); 1153 if (GNUNET_OK != 1154 TMH_base_url_by_connection (con, 1155 instance_id, 1156 &buf)) 1157 { 1158 GNUNET_break (0); 1159 return NULL; 1160 } 1161 GNUNET_buffer_write_path (&buf, 1162 "/orders"); 1163 GNUNET_buffer_write_path (&buf, 1164 order_id); 1165 if ( (NULL != claim_token) && 1166 (! GNUNET_is_zero (claim_token)) ) 1167 { 1168 /* 'token=' for human readability */ 1169 GNUNET_buffer_write_str (&buf, 1170 "?token="); 1171 GNUNET_buffer_write_data_encoded (&buf, 1172 (char *) claim_token, 1173 sizeof (*claim_token)); 1174 num_qp++; 1175 } 1176 1177 if (NULL != session_id) 1178 { 1179 if (num_qp > 0) 1180 GNUNET_buffer_write_str (&buf, 1181 "&session_id="); 1182 else 1183 GNUNET_buffer_write_str (&buf, 1184 "?session_id="); 1185 GNUNET_buffer_write_str (&buf, 1186 session_id); 1187 num_qp++; 1188 } 1189 1190 if (NULL != h_contract) 1191 { 1192 if (num_qp > 0) 1193 GNUNET_buffer_write_str (&buf, 1194 "&h_contract="); 1195 else 1196 GNUNET_buffer_write_str (&buf, 1197 "?h_contract="); 1198 GNUNET_buffer_write_data_encoded (&buf, 1199 (char *) h_contract, 1200 sizeof (*h_contract)); 1201 } 1202 1203 return GNUNET_buffer_reap_str (&buf); 1204 } 1205 1206 1207 char * 1208 TMH_make_taler_pay_uri (struct MHD_Connection *con, 1209 const char *order_id, 1210 const char *session_id, 1211 const char *instance_id, 1212 struct TALER_ClaimTokenP *claim_token) 1213 { 1214 struct GNUNET_Buffer buf; 1215 1216 GNUNET_assert (NULL != instance_id); 1217 GNUNET_assert (NULL != order_id); 1218 if (GNUNET_OK != 1219 TMH_taler_uri_by_connection (con, 1220 "pay", 1221 instance_id, 1222 &buf)) 1223 { 1224 GNUNET_break (0); 1225 return NULL; 1226 } 1227 GNUNET_buffer_write_path (&buf, 1228 order_id); 1229 GNUNET_buffer_write_path (&buf, 1230 (NULL == session_id) 1231 ? "" 1232 : session_id); 1233 if ( (NULL != claim_token) && 1234 (! GNUNET_is_zero (claim_token))) 1235 { 1236 /* Just 'c=' because this goes into QR 1237 codes, so this is more compact. */ 1238 GNUNET_buffer_write_str (&buf, 1239 "?c="); 1240 GNUNET_buffer_write_data_encoded (&buf, 1241 (char *) claim_token, 1242 sizeof (struct TALER_ClaimTokenP)); 1243 } 1244 1245 return GNUNET_buffer_reap_str (&buf); 1246 }