taler-merchant-httpd_patch-management-instances-INSTANCE.c (18977B)
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_patch-management-instances-INSTANCE.c 22 * @brief implementing PATCH /instances/$ID request handling 23 * @author Christian Grothoff 24 */ 25 #include "platform.h" 26 #include "taler-merchant-httpd_patch-management-instances-INSTANCE.h" 27 #include "taler-merchant-httpd_helper.h" 28 #include <taler/taler_json_lib.h> 29 #include <taler/taler_dbevents.h> 30 #include "taler-merchant-httpd_mfa.h" 31 #include "merchant-database/update_instance.h" 32 #include "merchant-database/start.h" 33 34 35 /** 36 * How often do we retry the simple INSERT database transaction? 37 */ 38 #define MAX_RETRIES 3 39 40 41 /** 42 * Free memory used by @a wm 43 * 44 * @param wm wire method to free 45 */ 46 static void 47 free_wm (struct TMH_WireMethod *wm) 48 { 49 GNUNET_free (wm->payto_uri.full_payto); 50 GNUNET_free (wm->wire_method); 51 GNUNET_free (wm); 52 } 53 54 55 /** 56 * PATCH configuration of an existing instance, given its configuration. 57 * 58 * @param mi instance to patch 59 * @param mfa_check true if a MFA check is required 60 * @param connection the MHD connection to handle 61 * @param[in,out] hc context with further information about the request 62 * @return MHD result code 63 */ 64 static enum MHD_Result 65 patch_instances_ID (struct TMH_MerchantInstance *mi, 66 bool mfa_check, 67 struct MHD_Connection *connection, 68 struct TMH_HandlerContext *hc) 69 { 70 struct TALER_MERCHANTDB_InstanceSettings is; 71 const char *name; 72 struct TMH_WireMethod *wm_head = NULL; 73 struct TMH_WireMethod *wm_tail = NULL; 74 const char *iphone = NULL; 75 bool no_transfer_delay; 76 bool no_pay_delay; 77 bool no_refund_delay; 78 struct GNUNET_JSON_Specification spec[] = { 79 GNUNET_JSON_spec_string ("name", 80 &name), 81 GNUNET_JSON_spec_mark_optional ( 82 GNUNET_JSON_spec_string ("website", 83 (const char **) &is.website), 84 NULL), 85 GNUNET_JSON_spec_mark_optional ( 86 GNUNET_JSON_spec_string ("email", 87 (const char **) &is.email), 88 NULL), 89 GNUNET_JSON_spec_mark_optional ( 90 GNUNET_JSON_spec_string ("phone_number", 91 &iphone), 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_json ("address", 98 &is.address), 99 GNUNET_JSON_spec_json ("jurisdiction", 100 &is.jurisdiction), 101 GNUNET_JSON_spec_bool ("use_stefan", 102 &is.use_stefan), 103 GNUNET_JSON_spec_mark_optional ( 104 GNUNET_JSON_spec_relative_time ("default_pay_delay", 105 &is.default_pay_delay), 106 &no_pay_delay), 107 GNUNET_JSON_spec_mark_optional ( 108 GNUNET_JSON_spec_relative_time ("default_refund_delay", 109 &is.default_refund_delay), 110 &no_refund_delay), 111 GNUNET_JSON_spec_mark_optional ( 112 GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay", 113 &is.default_wire_transfer_delay), 114 &no_transfer_delay), 115 GNUNET_JSON_spec_mark_optional ( 116 GNUNET_JSON_spec_time_rounder_interval ( 117 "default_wire_transfer_rounding_interval", 118 &is.default_wire_transfer_rounding_interval), 119 NULL), 120 GNUNET_JSON_spec_end () 121 }; 122 enum GNUNET_DB_QueryStatus qs; 123 124 GNUNET_assert (NULL != mi); 125 memset (&is, 126 0, 127 sizeof (is)); 128 { 129 enum GNUNET_GenericReturnValue res; 130 131 res = TALER_MHD_parse_json_data (connection, 132 hc->request_body, 133 spec); 134 if (GNUNET_OK != res) 135 return (GNUNET_NO == res) 136 ? MHD_YES 137 : MHD_NO; 138 } 139 if (! TMH_location_object_valid (is.address)) 140 { 141 GNUNET_break_op (0); 142 GNUNET_JSON_parse_free (spec); 143 return TALER_MHD_reply_with_error (connection, 144 MHD_HTTP_BAD_REQUEST, 145 TALER_EC_GENERIC_PARAMETER_MALFORMED, 146 "address"); 147 } 148 if ( (NULL != is.logo) && 149 (! TALER_MERCHANT_image_data_url_valid (is.logo)) ) 150 { 151 GNUNET_break_op (0); 152 GNUNET_JSON_parse_free (spec); 153 return TALER_MHD_reply_with_error (connection, 154 MHD_HTTP_BAD_REQUEST, 155 TALER_EC_GENERIC_PARAMETER_MALFORMED, 156 "logo"); 157 } 158 159 if (! TMH_location_object_valid (is.jurisdiction)) 160 { 161 GNUNET_break_op (0); 162 GNUNET_JSON_parse_free (spec); 163 return TALER_MHD_reply_with_error (connection, 164 MHD_HTTP_BAD_REQUEST, 165 TALER_EC_GENERIC_PARAMETER_MALFORMED, 166 "jurisdiction"); 167 } 168 169 if (no_transfer_delay) 170 is.default_wire_transfer_delay = mi->settings.default_wire_transfer_delay; 171 if (no_pay_delay) 172 is.default_pay_delay = mi->settings.default_pay_delay; 173 if (no_refund_delay) 174 is.default_refund_delay = mi->settings.default_refund_delay; 175 if (GNUNET_TIME_relative_is_forever (is.default_pay_delay)) 176 { 177 GNUNET_break_op (0); 178 GNUNET_JSON_parse_free (spec); 179 return TALER_MHD_reply_with_error (connection, 180 MHD_HTTP_BAD_REQUEST, 181 TALER_EC_GENERIC_PARAMETER_MALFORMED, 182 "default_pay_delay"); 183 } 184 if (GNUNET_TIME_relative_is_forever (is.default_refund_delay)) 185 { 186 GNUNET_break_op (0); 187 GNUNET_JSON_parse_free (spec); 188 return TALER_MHD_reply_with_error (connection, 189 MHD_HTTP_BAD_REQUEST, 190 TALER_EC_GENERIC_PARAMETER_MALFORMED, 191 "default_refund_delay"); 192 } 193 if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay)) 194 { 195 GNUNET_break_op (0); 196 GNUNET_JSON_parse_free (spec); 197 return TALER_MHD_reply_with_error (connection, 198 MHD_HTTP_BAD_REQUEST, 199 TALER_EC_GENERIC_PARAMETER_MALFORMED, 200 "default_wire_transfer_delay"); 201 } 202 if (NULL != iphone) 203 { 204 is.phone = TALER_MERCHANT_phone_validate_normalize (iphone, 205 false); 206 if (NULL == is.phone) 207 { 208 GNUNET_break_op (0); 209 GNUNET_JSON_parse_free (spec); 210 return TALER_MHD_reply_with_error (connection, 211 MHD_HTTP_BAD_REQUEST, 212 TALER_EC_GENERIC_PARAMETER_MALFORMED, 213 "phone_number"); 214 } 215 if ( (NULL != TMH_phone_regex) && 216 (0 != 217 regexec (&TMH_phone_rx, 218 is.phone, 219 0, 220 NULL, 221 0)) ) 222 { 223 GNUNET_break_op (0); 224 GNUNET_JSON_parse_free (spec); 225 return TALER_MHD_reply_with_error (connection, 226 MHD_HTTP_BAD_REQUEST, 227 TALER_EC_GENERIC_PARAMETER_MALFORMED, 228 "phone_number"); 229 } 230 } 231 if ( (NULL != is.email) && 232 (! TALER_MERCHANT_email_valid (is.email)) ) 233 { 234 GNUNET_break_op (0); 235 GNUNET_JSON_parse_free (spec); 236 GNUNET_free (is.phone); 237 return TALER_MHD_reply_with_error (connection, 238 MHD_HTTP_BAD_REQUEST, 239 TALER_EC_GENERIC_PARAMETER_MALFORMED, 240 "email"); 241 } 242 if ( (NULL != is.phone) && 243 (NULL != mi->settings.phone) && 244 (0 == strcmp (mi->settings.phone, 245 is.phone)) ) 246 is.phone_validated = mi->settings.phone_validated; 247 if ( (NULL != is.email) && 248 (NULL != mi->settings.email) && 249 (0 == strcmp (mi->settings.email, 250 is.email)) ) 251 is.email_validated = mi->settings.email_validated; 252 if (mfa_check) 253 { 254 enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; 255 enum TEH_TanChannelSet mtc = TEH_mandatory_tan_channels; 256 257 if ( (0 != (mtc & TMH_TCS_SMS)) && 258 (NULL != mi->settings.phone) && 259 (NULL == is.phone) ) 260 { 261 GNUNET_break_op (0); 262 GNUNET_JSON_parse_free (spec); 263 GNUNET_free (is.phone); 264 return TALER_MHD_reply_with_error ( 265 connection, 266 MHD_HTTP_BAD_REQUEST, 267 TALER_EC_GENERIC_PARAMETER_MISSING, 268 "phone_number"); 269 } 270 if ( (0 != (mtc & TMH_TCS_EMAIL)) && 271 (NULL != mi->settings.email) && 272 (NULL == is.email) ) 273 { 274 GNUNET_break_op (0); 275 GNUNET_JSON_parse_free (spec); 276 GNUNET_free (is.phone); 277 return TALER_MHD_reply_with_error ( 278 connection, 279 MHD_HTTP_BAD_REQUEST, 280 TALER_EC_GENERIC_PARAMETER_MISSING, 281 "email"); 282 } 283 if ( (is.phone_validated || 284 (NULL == is.phone) ) && 285 (0 != (mtc & TMH_TCS_SMS)) ) 286 mtc -= TMH_TCS_SMS; 287 if ( (is.email_validated || 288 (NULL == is.email) ) && 289 (0 != (mtc & TMH_TCS_EMAIL)) ) 290 mtc -= TMH_TCS_EMAIL; 291 switch (mtc) 292 { 293 case TMH_TCS_NONE: 294 ret = GNUNET_OK; 295 break; 296 case TMH_TCS_SMS: 297 GNUNET_assert (NULL != is.phone); 298 is.phone_validated = true; 299 /* validate new phone number, if possible require old e-mail 300 address for authorization */ 301 ret = TMH_mfa_challenges_do (hc, 302 mi->settings.id, 303 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 304 true, 305 TALER_MERCHANT_MFA_CHANNEL_SMS, 306 is.phone, 307 0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL 308 & TEH_mandatory_tan_channels) 309 ? TALER_MERCHANT_MFA_CHANNEL_NONE 310 : TALER_MERCHANT_MFA_CHANNEL_EMAIL, 311 mi->settings.email, 312 TALER_MERCHANT_MFA_CHANNEL_NONE); 313 break; 314 case TMH_TCS_EMAIL: 315 GNUNET_assert (NULL != is.email); 316 is.email_validated = true; 317 /* validate new e-mail address, if possible require old phone 318 address for authorization */ 319 ret = TMH_mfa_challenges_do (hc, 320 mi->settings.id, 321 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 322 true, 323 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 324 is.email, 325 0 == (TALER_MERCHANT_MFA_CHANNEL_SMS 326 & TEH_mandatory_tan_channels) 327 ? TALER_MERCHANT_MFA_CHANNEL_NONE 328 : TALER_MERCHANT_MFA_CHANNEL_SMS, 329 mi->settings.phone, 330 TALER_MERCHANT_MFA_CHANNEL_NONE); 331 break; 332 case TMH_TCS_EMAIL_AND_SMS: 333 GNUNET_assert (NULL != is.phone); 334 GNUNET_assert (NULL != is.email); 335 is.phone_validated = true; 336 is.email_validated = true; 337 /* To change both, we require both old and both new 338 addresses to consent */ 339 ret = TMH_mfa_challenges_do (hc, 340 mi->settings.id, 341 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 342 true, 343 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 344 mi->settings.email, 345 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 346 is.email, 347 TALER_MERCHANT_MFA_CHANNEL_SMS, 348 mi->settings.phone, 349 TALER_MERCHANT_MFA_CHANNEL_SMS, 350 is.phone, 351 TALER_MERCHANT_MFA_CHANNEL_NONE); 352 break; 353 } 354 if (GNUNET_OK != ret) 355 { 356 GNUNET_JSON_parse_free (spec); 357 GNUNET_free (is.phone); 358 return (GNUNET_NO == ret) 359 ? MHD_YES 360 : MHD_NO; 361 } 362 } 363 364 for (unsigned int retry = 0; retry<MAX_RETRIES; retry++) 365 { 366 /* Cleanup after earlier loops */ 367 { 368 struct TMH_WireMethod *wm; 369 370 while (NULL != (wm = wm_head)) 371 { 372 GNUNET_CONTAINER_DLL_remove (wm_head, 373 wm_tail, 374 wm); 375 free_wm (wm); 376 } 377 } 378 if (GNUNET_OK != 379 TALER_MERCHANTDB_start (TMH_db, 380 "PATCH /instances")) 381 { 382 GNUNET_JSON_parse_free (spec); 383 GNUNET_free (is.phone); 384 return TALER_MHD_reply_with_error (connection, 385 MHD_HTTP_INTERNAL_SERVER_ERROR, 386 TALER_EC_GENERIC_DB_START_FAILED, 387 NULL); 388 } 389 /* Check for equality of settings */ 390 if (! ( (0 == strcmp (mi->settings.name, 391 name)) && 392 ((mi->settings.email == is.email) || 393 (NULL != is.email && NULL != mi->settings.email && 394 0 == strcmp (mi->settings.email, 395 is.email))) && 396 ((mi->settings.phone == is.phone) || 397 (NULL != is.phone && NULL != mi->settings.phone && 398 0 == strcmp (mi->settings.phone, 399 is.phone))) && 400 ((mi->settings.website == is.website) || 401 (NULL != is.website && NULL != mi->settings.website && 402 0 == strcmp (mi->settings.website, 403 is.website))) && 404 ((mi->settings.logo == is.logo) || 405 (NULL != is.logo && NULL != mi->settings.logo && 406 0 == strcmp (mi->settings.logo, 407 is.logo))) && 408 (1 == json_equal (mi->settings.address, 409 is.address)) && 410 (1 == json_equal (mi->settings.jurisdiction, 411 is.jurisdiction)) && 412 (mi->settings.use_stefan == is.use_stefan) && 413 (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay, 414 ==, 415 is.default_wire_transfer_delay)) && 416 (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay, 417 ==, 418 is.default_refund_delay)) && 419 (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay, 420 ==, 421 is.default_pay_delay)) ) ) 422 { 423 is.id = mi->settings.id; 424 is.name = GNUNET_strdup (name); 425 qs = TALER_MERCHANTDB_update_instance (TMH_db, 426 &is); 427 GNUNET_free (is.name); 428 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 429 { 430 TALER_MERCHANTDB_rollback (TMH_db); 431 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 432 goto retry; 433 else 434 goto giveup; 435 } 436 } 437 qs = TALER_MERCHANTDB_commit (TMH_db); 438 retry: 439 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 440 continue; 441 break; 442 } /* for(... MAX_RETRIES) */ 443 giveup: 444 /* Update our 'settings' */ 445 GNUNET_free (mi->settings.name); 446 GNUNET_free (mi->settings.email); 447 GNUNET_free (mi->settings.phone); 448 GNUNET_free (mi->settings.website); 449 GNUNET_free (mi->settings.logo); 450 json_decref (mi->settings.address); 451 json_decref (mi->settings.jurisdiction); 452 is.id = mi->settings.id; 453 mi->settings = is; 454 mi->settings.address = json_incref (mi->settings.address); 455 mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction); 456 mi->settings.name = GNUNET_strdup (name); 457 if (NULL != is.email) 458 mi->settings.email = GNUNET_strdup (is.email); 459 mi->settings.phone = is.phone; 460 is.phone = NULL; 461 if (NULL != is.website) 462 mi->settings.website = GNUNET_strdup (is.website); 463 if (NULL != is.logo) 464 mi->settings.logo = GNUNET_strdup (is.logo); 465 466 GNUNET_JSON_parse_free (spec); 467 TMH_reload_instances (mi->settings.id); 468 return TALER_MHD_reply_static (connection, 469 MHD_HTTP_NO_CONTENT, 470 NULL, 471 NULL, 472 0); 473 } 474 475 476 enum MHD_Result 477 TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh, 478 struct MHD_Connection *connection, 479 struct TMH_HandlerContext *hc) 480 { 481 struct TMH_MerchantInstance *mi = hc->instance; 482 483 return patch_instances_ID (mi, 484 true, 485 connection, 486 hc); 487 } 488 489 490 enum MHD_Result 491 TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh, 492 struct MHD_Connection *connection, 493 struct TMH_HandlerContext *hc) 494 { 495 struct TMH_MerchantInstance *mi; 496 497 mi = TMH_lookup_instance (hc->infix); 498 if (NULL == mi) 499 { 500 return TALER_MHD_reply_with_error (connection, 501 MHD_HTTP_NOT_FOUND, 502 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 503 hc->infix); 504 } 505 if (mi->deleted) 506 { 507 return TALER_MHD_reply_with_error (connection, 508 MHD_HTTP_CONFLICT, 509 TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED, 510 hc->infix); 511 } 512 return patch_instances_ID (mi, 513 false, 514 connection, 515 hc); 516 } 517 518 519 /* end of taler-merchant-httpd_patch-management-instances-INSTANCE.c */