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