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