taler-merchant-httpd_post-management-instances.c (25005B)
1 /* 2 This file is part of TALER 3 (C) 2020-2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file taler-merchant-httpd_post-management-instances.c 22 * @brief implementing POST /instances request handling 23 * @author Christian Grothoff 24 */ 25 #include "taler/platform.h" 26 #include "taler-merchant-httpd_post-management-instances.h" 27 #include "taler-merchant-httpd_helper.h" 28 #include "taler-merchant-httpd.h" 29 #include "taler-merchant-httpd_auth.h" 30 #include "taler-merchant-httpd_mfa.h" 31 #include "taler/taler_merchant_bank_lib.h" 32 #include <taler/taler_dbevents.h> 33 #include <taler/taler_json_lib.h> 34 #include <regex.h> 35 36 /** 37 * How often do we retry the simple INSERT database transaction? 38 */ 39 #define MAX_RETRIES 3 40 41 42 /** 43 * Generate an instance, given its configuration. 44 * 45 * @param rh context of the handler 46 * @param connection the MHD connection to handle 47 * @param[in,out] hc context with further information about the request 48 * @param login_token_expiration set to how long a login token validity 49 * should be, use zero if no login token should be created 50 * @param validation_needed true if self-provisioned and 51 * email/phone registration is required before the 52 * instance can become fully active 53 * @return MHD result code 54 */ 55 static MHD_RESULT 56 post_instances (const struct TMH_RequestHandler *rh, 57 struct MHD_Connection *connection, 58 struct TMH_HandlerContext *hc, 59 struct GNUNET_TIME_Relative login_token_expiration, 60 bool validation_needed) 61 { 62 struct TALER_MERCHANTDB_InstanceSettings is = { 0 }; 63 struct TALER_MERCHANTDB_InstanceAuthSettings ias; 64 const char *auth_password = NULL; 65 struct TMH_WireMethod *wm_head = NULL; 66 struct TMH_WireMethod *wm_tail = NULL; 67 const json_t *jauth; 68 const char *iphone = NULL; 69 bool no_pay_delay; 70 bool no_refund_delay; 71 bool no_transfer_delay; 72 bool no_rounding_interval; 73 struct GNUNET_JSON_Specification spec[] = { 74 GNUNET_JSON_spec_string ("id", 75 (const char **) &is.id), 76 GNUNET_JSON_spec_string ("name", 77 (const char **) &is.name), 78 GNUNET_JSON_spec_mark_optional ( 79 GNUNET_JSON_spec_string ("email", 80 (const char **) &is.email), 81 NULL), 82 GNUNET_JSON_spec_mark_optional ( 83 GNUNET_JSON_spec_string ("phone_number", 84 &iphone), 85 NULL), 86 GNUNET_JSON_spec_mark_optional ( 87 GNUNET_JSON_spec_string ("website", 88 (const char **) &is.website), 89 NULL), 90 GNUNET_JSON_spec_mark_optional ( 91 GNUNET_JSON_spec_string ("logo", 92 (const char **) &is.logo), 93 NULL), 94 GNUNET_JSON_spec_object_const ("auth", 95 &jauth), 96 GNUNET_JSON_spec_json ("address", 97 &is.address), 98 GNUNET_JSON_spec_json ("jurisdiction", 99 &is.jurisdiction), 100 GNUNET_JSON_spec_bool ("use_stefan", 101 &is.use_stefan), 102 GNUNET_JSON_spec_mark_optional ( 103 GNUNET_JSON_spec_relative_time ("default_pay_delay", 104 &is.default_pay_delay), 105 &no_pay_delay), 106 GNUNET_JSON_spec_mark_optional ( 107 GNUNET_JSON_spec_relative_time ("default_refund_delay", 108 &is.default_refund_delay), 109 &no_refund_delay), 110 GNUNET_JSON_spec_mark_optional ( 111 GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay", 112 &is.default_wire_transfer_delay), 113 &no_transfer_delay), 114 GNUNET_JSON_spec_mark_optional ( 115 GNUNET_JSON_spec_time_rounder_interval ( 116 "default_wire_transfer_rounding_interval", 117 &is.default_wire_transfer_rounding_interval), 118 &no_rounding_interval), 119 GNUNET_JSON_spec_end () 120 }; 121 122 { 123 enum GNUNET_GenericReturnValue res; 124 125 res = TALER_MHD_parse_json_data (connection, 126 hc->request_body, 127 spec); 128 if (GNUNET_OK != res) 129 return (GNUNET_NO == res) 130 ? MHD_YES 131 : MHD_NO; 132 } 133 if (no_pay_delay) 134 is.default_pay_delay = TMH_default_pay_delay; 135 if (no_refund_delay) 136 is.default_refund_delay = TMH_default_refund_delay; 137 if (no_transfer_delay) 138 is.default_wire_transfer_delay = TMH_default_wire_transfer_delay; 139 if (no_rounding_interval) 140 is.default_wire_transfer_rounding_interval 141 = TMH_default_wire_transfer_rounding_interval; 142 if (GNUNET_TIME_relative_is_forever (is.default_pay_delay)) 143 { 144 GNUNET_break_op (0); 145 GNUNET_JSON_parse_free (spec); 146 return TALER_MHD_reply_with_error (connection, 147 MHD_HTTP_BAD_REQUEST, 148 TALER_EC_GENERIC_PARAMETER_MALFORMED, 149 "default_pay_delay"); 150 } 151 if (GNUNET_TIME_relative_is_forever (is.default_refund_delay)) 152 { 153 GNUNET_break_op (0); 154 GNUNET_JSON_parse_free (spec); 155 return TALER_MHD_reply_with_error (connection, 156 MHD_HTTP_BAD_REQUEST, 157 TALER_EC_GENERIC_PARAMETER_MALFORMED, 158 "default_refund_delay"); 159 } 160 if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay)) 161 { 162 GNUNET_break_op (0); 163 GNUNET_JSON_parse_free (spec); 164 return TALER_MHD_reply_with_error (connection, 165 MHD_HTTP_BAD_REQUEST, 166 TALER_EC_GENERIC_PARAMETER_MALFORMED, 167 "default_wire_transfer_delay"); 168 } 169 if (NULL != iphone) 170 { 171 is.phone = TALER_MERCHANT_phone_validate_normalize (iphone, 172 false); 173 if (NULL == is.phone) 174 { 175 GNUNET_break_op (0); 176 GNUNET_JSON_parse_free (spec); 177 return TALER_MHD_reply_with_error (connection, 178 MHD_HTTP_BAD_REQUEST, 179 TALER_EC_GENERIC_PARAMETER_MALFORMED, 180 "phone_number"); 181 } 182 if ( (NULL != TMH_phone_regex) && 183 (0 != 184 regexec (&TMH_phone_rx, 185 is.phone, 186 0, 187 NULL, 188 0)) ) 189 { 190 GNUNET_break_op (0); 191 GNUNET_JSON_parse_free (spec); 192 return TALER_MHD_reply_with_error (connection, 193 MHD_HTTP_BAD_REQUEST, 194 TALER_EC_GENERIC_PARAMETER_MALFORMED, 195 "phone_number"); 196 } 197 } 198 if ( (NULL != is.email) && 199 (! TALER_MERCHANT_email_valid (is.email)) ) 200 { 201 GNUNET_break_op (0); 202 GNUNET_JSON_parse_free (spec); 203 GNUNET_free (is.phone); 204 return TALER_MHD_reply_with_error (connection, 205 MHD_HTTP_BAD_REQUEST, 206 TALER_EC_GENERIC_PARAMETER_MALFORMED, 207 "email"); 208 } 209 210 { 211 enum GNUNET_GenericReturnValue ret; 212 213 ret = TMH_check_auth_config (connection, 214 jauth, 215 &auth_password); 216 if (GNUNET_OK != ret) 217 { 218 GNUNET_free (is.phone); 219 GNUNET_JSON_parse_free (spec); 220 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 221 } 222 } 223 224 /* check 'id' well-formed */ 225 { 226 static bool once; 227 static regex_t reg; 228 bool id_wellformed = true; 229 230 if (! once) 231 { 232 once = true; 233 GNUNET_assert (0 == 234 regcomp (®, 235 "^[A-Za-z0-9][A-Za-z0-9_.@-]+$", 236 REG_EXTENDED)); 237 } 238 239 if (0 != regexec (®, 240 is.id, 241 0, NULL, 0)) 242 id_wellformed = false; 243 if (! id_wellformed) 244 { 245 GNUNET_JSON_parse_free (spec); 246 GNUNET_free (is.phone); 247 return TALER_MHD_reply_with_error (connection, 248 MHD_HTTP_BAD_REQUEST, 249 TALER_EC_GENERIC_PARAMETER_MALFORMED, 250 "id"); 251 } 252 } 253 254 if (! TMH_location_object_valid (is.address)) 255 { 256 GNUNET_break_op (0); 257 GNUNET_JSON_parse_free (spec); 258 GNUNET_free (is.phone); 259 return TALER_MHD_reply_with_error (connection, 260 MHD_HTTP_BAD_REQUEST, 261 TALER_EC_GENERIC_PARAMETER_MALFORMED, 262 "address"); 263 } 264 265 if (! TMH_location_object_valid (is.jurisdiction)) 266 { 267 GNUNET_break_op (0); 268 GNUNET_JSON_parse_free (spec); 269 GNUNET_free (is.phone); 270 return TALER_MHD_reply_with_error (connection, 271 MHD_HTTP_BAD_REQUEST, 272 TALER_EC_GENERIC_PARAMETER_MALFORMED, 273 "jurisdiction"); 274 } 275 276 if ( (NULL != is.logo) && 277 (! TALER_MERCHANT_image_data_url_valid (is.logo)) ) 278 { 279 GNUNET_break_op (0); 280 GNUNET_JSON_parse_free (spec); 281 GNUNET_free (is.phone); 282 return TALER_MHD_reply_with_error (connection, 283 MHD_HTTP_BAD_REQUEST, 284 TALER_EC_GENERIC_PARAMETER_MALFORMED, 285 "logo"); 286 } 287 288 { 289 /* Test if an instance of this id is known */ 290 struct TMH_MerchantInstance *mi; 291 292 mi = TMH_lookup_instance (is.id); 293 if (NULL != mi) 294 { 295 if (mi->deleted) 296 { 297 GNUNET_JSON_parse_free (spec); 298 GNUNET_free (is.phone); 299 return TALER_MHD_reply_with_error (connection, 300 MHD_HTTP_CONFLICT, 301 TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED, 302 is.id); 303 } 304 /* Check for idempotency */ 305 if ( (0 == strcmp (mi->settings.id, 306 is.id)) && 307 (0 == strcmp (mi->settings.name, 308 is.name)) && 309 ((mi->settings.email == is.email) || 310 (NULL != is.email && NULL != mi->settings.email && 311 0 == strcmp (mi->settings.email, 312 is.email))) && 313 ((mi->settings.website == is.website) || 314 (NULL != is.website && NULL != mi->settings.website && 315 0 == strcmp (mi->settings.website, 316 is.website))) && 317 ((mi->settings.logo == is.logo) || 318 (NULL != is.logo && NULL != mi->settings.logo && 319 0 == strcmp (mi->settings.logo, 320 is.logo))) && 321 ( ( (NULL != auth_password) && 322 (GNUNET_OK == 323 TMH_check_auth (auth_password, 324 &mi->auth.auth_salt, 325 &mi->auth.auth_hash)) ) || 326 ( (NULL == auth_password) && 327 (GNUNET_YES == 328 GNUNET_is_zero (&mi->auth.auth_hash))) ) && 329 (1 == json_equal (mi->settings.address, 330 is.address)) && 331 (1 == json_equal (mi->settings.jurisdiction, 332 is.jurisdiction)) && 333 (mi->settings.use_stefan == is.use_stefan) && 334 (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay, 335 ==, 336 is.default_wire_transfer_delay)) && 337 (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay, 338 ==, 339 is.default_pay_delay)) && 340 (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay, 341 ==, 342 is.default_refund_delay)) ) 343 { 344 GNUNET_JSON_parse_free (spec); 345 GNUNET_free (is.phone); 346 return TALER_MHD_reply_static (connection, 347 MHD_HTTP_NO_CONTENT, 348 NULL, 349 NULL, 350 0); 351 } 352 GNUNET_JSON_parse_free (spec); 353 GNUNET_free (is.phone); 354 return TALER_MHD_reply_with_error (connection, 355 MHD_HTTP_CONFLICT, 356 TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS, 357 is.id); 358 } 359 } 360 361 /* Check MFA is satisfied */ 362 if (validation_needed) 363 { 364 enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; 365 366 if ( (0 != (TMH_TCS_SMS & TEH_mandatory_tan_channels)) && 367 (NULL == is.phone) ) 368 { 369 GNUNET_break_op (0); 370 GNUNET_JSON_parse_free (spec); 371 GNUNET_free (is.phone); /* does nothing... */ 372 return TALER_MHD_reply_with_error (connection, 373 MHD_HTTP_BAD_REQUEST, 374 TALER_EC_GENERIC_PARAMETER_MISSING, 375 "phone_number"); 376 377 } 378 if ( (0 != (TMH_TCS_EMAIL & TEH_mandatory_tan_channels)) && 379 (NULL == is.email) ) 380 { 381 GNUNET_break_op (0); 382 GNUNET_JSON_parse_free (spec); 383 GNUNET_free (is.phone); 384 return TALER_MHD_reply_with_error (connection, 385 MHD_HTTP_BAD_REQUEST, 386 TALER_EC_GENERIC_PARAMETER_MISSING, 387 "email"); 388 } 389 switch (TEH_mandatory_tan_channels) 390 { 391 case TMH_TCS_NONE: 392 GNUNET_assert (0); 393 ret = GNUNET_OK; 394 break; 395 case TMH_TCS_SMS: 396 is.phone_validated = true; 397 ret = TMH_mfa_challenges_do (hc, 398 TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION, 399 true, 400 TALER_MERCHANT_MFA_CHANNEL_SMS, 401 is.phone, 402 TALER_MERCHANT_MFA_CHANNEL_NONE); 403 break; 404 case TMH_TCS_EMAIL: 405 is.email_validated = true; 406 ret = TMH_mfa_challenges_do (hc, 407 TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION, 408 true, 409 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 410 is.email, 411 TALER_MERCHANT_MFA_CHANNEL_NONE); 412 break; 413 case TMH_TCS_EMAIL_AND_SMS: 414 is.phone_validated = true; 415 is.email_validated = true; 416 ret = TMH_mfa_challenges_do (hc, 417 TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION, 418 true, 419 TALER_MERCHANT_MFA_CHANNEL_SMS, 420 is.phone, 421 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 422 is.email, 423 TALER_MERCHANT_MFA_CHANNEL_NONE); 424 break; 425 } 426 if (GNUNET_OK != ret) 427 { 428 GNUNET_JSON_parse_free (spec); 429 GNUNET_free (is.phone); 430 return (GNUNET_NO == ret) 431 ? MHD_YES 432 : MHD_NO; 433 } 434 } 435 436 /* handle authentication token setup */ 437 if (NULL == auth_password) 438 { 439 memset (&ias.auth_salt, 440 0, 441 sizeof (ias.auth_salt)); 442 memset (&ias.auth_hash, 443 0, 444 sizeof (ias.auth_hash)); 445 } 446 else 447 { 448 /* Sets 'auth_salt' and 'auth_hash' */ 449 TMH_compute_auth (auth_password, 450 &ias.auth_salt, 451 &ias.auth_hash); 452 } 453 454 /* create in-memory data structure */ 455 { 456 struct TMH_MerchantInstance *mi; 457 enum GNUNET_DB_QueryStatus qs; 458 459 mi = GNUNET_new (struct TMH_MerchantInstance); 460 mi->wm_head = wm_head; 461 mi->wm_tail = wm_tail; 462 mi->settings = is; 463 mi->settings.address = json_incref (mi->settings.address); 464 mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction); 465 mi->settings.id = GNUNET_strdup (is.id); 466 mi->settings.name = GNUNET_strdup (is.name); 467 if (NULL != is.email) 468 mi->settings.email = GNUNET_strdup (is.email); 469 mi->settings.phone = is.phone; 470 is.phone = NULL; 471 if (NULL != is.website) 472 mi->settings.website = GNUNET_strdup (is.website); 473 if (NULL != is.logo) 474 mi->settings.logo = GNUNET_strdup (is.logo); 475 mi->auth = ias; 476 mi->validation_needed = validation_needed; 477 GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv); 478 GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv, 479 &mi->merchant_pub.eddsa_pub); 480 481 for (unsigned int i = 0; i<MAX_RETRIES; i++) 482 { 483 if (GNUNET_OK != 484 TMH_db->start (TMH_db->cls, 485 "post /instances")) 486 { 487 mi->rc = 1; 488 TMH_instance_decref (mi); 489 GNUNET_JSON_parse_free (spec); 490 return TALER_MHD_reply_with_error (connection, 491 MHD_HTTP_INTERNAL_SERVER_ERROR, 492 TALER_EC_GENERIC_DB_START_FAILED, 493 NULL); 494 } 495 qs = TMH_db->insert_instance (TMH_db->cls, 496 &mi->merchant_pub, 497 &mi->merchant_priv, 498 &mi->settings, 499 &mi->auth, 500 validation_needed); 501 switch (qs) 502 { 503 case GNUNET_DB_STATUS_HARD_ERROR: 504 { 505 MHD_RESULT ret; 506 507 TMH_db->rollback (TMH_db->cls); 508 GNUNET_break (0); 509 ret = TALER_MHD_reply_with_error (connection, 510 MHD_HTTP_INTERNAL_SERVER_ERROR, 511 TALER_EC_GENERIC_DB_STORE_FAILED, 512 is.id); 513 mi->rc = 1; 514 TMH_instance_decref (mi); 515 GNUNET_JSON_parse_free (spec); 516 return ret; 517 } 518 case GNUNET_DB_STATUS_SOFT_ERROR: 519 goto retry; 520 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 521 { 522 MHD_RESULT ret; 523 524 TMH_db->rollback (TMH_db->cls); 525 GNUNET_break (0); 526 ret = TALER_MHD_reply_with_error (connection, 527 MHD_HTTP_CONFLICT, 528 TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS, 529 is.id); 530 mi->rc = 1; 531 TMH_instance_decref (mi); 532 GNUNET_JSON_parse_free (spec); 533 return ret; 534 } 535 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 536 /* handled below */ 537 break; 538 } 539 qs = TMH_db->commit (TMH_db->cls); 540 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 541 qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 542 retry: 543 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 544 break; /* success! -- or hard failure */ 545 } /* for .. MAX_RETRIES */ 546 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 547 { 548 mi->rc = 1; 549 TMH_instance_decref (mi); 550 GNUNET_JSON_parse_free (spec); 551 return TALER_MHD_reply_with_error (connection, 552 MHD_HTTP_INTERNAL_SERVER_ERROR, 553 TALER_EC_GENERIC_DB_COMMIT_FAILED, 554 NULL); 555 } 556 /* Finally, also update our running process */ 557 GNUNET_assert (GNUNET_OK == 558 TMH_add_instance (mi)); 559 TMH_reload_instances (mi->settings.id); 560 } 561 GNUNET_JSON_parse_free (spec); 562 if (GNUNET_TIME_relative_is_zero (login_token_expiration)) 563 { 564 return TALER_MHD_reply_static (connection, 565 MHD_HTTP_NO_CONTENT, 566 NULL, 567 NULL, 568 0); 569 } 570 571 { 572 struct TALER_MERCHANTDB_LoginTokenP btoken; 573 enum TMH_AuthScope iscope = TMH_AS_REFRESHABLE | TMH_AS_SPA; 574 enum GNUNET_DB_QueryStatus qs; 575 struct GNUNET_TIME_Timestamp expiration_time; 576 bool refreshable = true; 577 578 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 579 &btoken, 580 sizeof (btoken)); 581 expiration_time 582 = GNUNET_TIME_relative_to_timestamp (login_token_expiration); 583 qs = TMH_db->insert_login_token (TMH_db->cls, 584 is.id, 585 &btoken, 586 GNUNET_TIME_timestamp_get (), 587 expiration_time, 588 iscope, 589 "login token from instance creation"); 590 switch (qs) 591 { 592 case GNUNET_DB_STATUS_HARD_ERROR: 593 case GNUNET_DB_STATUS_SOFT_ERROR: 594 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 595 GNUNET_break (0); 596 return TALER_MHD_reply_with_ec (connection, 597 TALER_EC_GENERIC_DB_STORE_FAILED, 598 "insert_login_token"); 599 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 600 break; 601 } 602 603 { 604 char *tok; 605 MHD_RESULT ret; 606 char *val; 607 608 val = GNUNET_STRINGS_data_to_string_alloc (&btoken, 609 sizeof (btoken)); 610 GNUNET_asprintf (&tok, 611 RFC_8959_PREFIX "%s", 612 val); 613 GNUNET_free (val); 614 ret = TALER_MHD_REPLY_JSON_PACK ( 615 connection, 616 MHD_HTTP_OK, 617 GNUNET_JSON_pack_string ("access_token", 618 tok), 619 GNUNET_JSON_pack_string ("token", 620 tok), 621 GNUNET_JSON_pack_string ("scope", 622 TMH_get_name_by_scope (iscope, 623 &refreshable)), 624 GNUNET_JSON_pack_bool ("refreshable", 625 refreshable), 626 GNUNET_JSON_pack_timestamp ("expiration", 627 expiration_time)); 628 GNUNET_free (tok); 629 return ret; 630 } 631 } 632 } 633 634 635 /** 636 * Generate an instance, given its configuration. 637 * 638 * @param rh context of the handler 639 * @param connection the MHD connection to handle 640 * @param[in,out] hc context with further information about the request 641 * @return MHD result code 642 */ 643 MHD_RESULT 644 TMH_private_post_instances (const struct TMH_RequestHandler *rh, 645 struct MHD_Connection *connection, 646 struct TMH_HandlerContext *hc) 647 { 648 return post_instances (rh, 649 connection, 650 hc, 651 GNUNET_TIME_UNIT_ZERO, 652 false); 653 } 654 655 656 /** 657 * Generate an instance, given its configuration. 658 * Public handler to be used when self-provisioning. 659 * 660 * @param rh context of the handler 661 * @param connection the MHD connection to handle 662 * @param[in,out] hc context with further information about the request 663 * @return MHD result code 664 */ 665 MHD_RESULT 666 TMH_public_post_instances (const struct TMH_RequestHandler *rh, 667 struct MHD_Connection *connection, 668 struct TMH_HandlerContext *hc) 669 { 670 struct GNUNET_TIME_Relative expiration; 671 672 TALER_MHD_parse_request_rel_time (connection, 673 "token_validity_ms", 674 &expiration); 675 if (GNUNET_YES != 676 TMH_have_self_provisioning) 677 { 678 GNUNET_break_op (0); 679 return TALER_MHD_reply_with_error (connection, 680 MHD_HTTP_FORBIDDEN, 681 TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED, 682 "Self-provisioning is not enabled"); 683 } 684 685 return post_instances (rh, 686 connection, 687 hc, 688 expiration, 689 TMH_TCS_NONE != 690 TEH_mandatory_tan_channels); 691 } 692 693 694 /* end of taler-merchant-httpd_post-management-instances.c */