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