taler-merchant-httpd_patch-management-instances-INSTANCE.c (18821B)
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 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 303 true, 304 TALER_MERCHANT_MFA_CHANNEL_SMS, 305 is.phone, 306 0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL 307 & TEH_mandatory_tan_channels) 308 ? TALER_MERCHANT_MFA_CHANNEL_NONE 309 : TALER_MERCHANT_MFA_CHANNEL_EMAIL, 310 mi->settings.email, 311 TALER_MERCHANT_MFA_CHANNEL_NONE); 312 break; 313 case TMH_TCS_EMAIL: 314 GNUNET_assert (NULL != is.email); 315 is.email_validated = true; 316 /* validate new e-mail address, if possible require old phone 317 address for authorization */ 318 ret = TMH_mfa_challenges_do (hc, 319 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 320 true, 321 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 322 is.email, 323 0 == (TALER_MERCHANT_MFA_CHANNEL_SMS 324 & TEH_mandatory_tan_channels) 325 ? TALER_MERCHANT_MFA_CHANNEL_NONE 326 : TALER_MERCHANT_MFA_CHANNEL_SMS, 327 mi->settings.phone, 328 TALER_MERCHANT_MFA_CHANNEL_NONE); 329 break; 330 case TMH_TCS_EMAIL_AND_SMS: 331 GNUNET_assert (NULL != is.phone); 332 GNUNET_assert (NULL != is.email); 333 is.phone_validated = true; 334 is.email_validated = true; 335 /* To change both, we require both old and both new 336 addresses to consent */ 337 ret = TMH_mfa_challenges_do (hc, 338 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 339 true, 340 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 341 mi->settings.email, 342 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 343 is.email, 344 TALER_MERCHANT_MFA_CHANNEL_SMS, 345 mi->settings.phone, 346 TALER_MERCHANT_MFA_CHANNEL_SMS, 347 is.phone, 348 TALER_MERCHANT_MFA_CHANNEL_NONE); 349 break; 350 } 351 if (GNUNET_OK != ret) 352 { 353 GNUNET_JSON_parse_free (spec); 354 GNUNET_free (is.phone); 355 return (GNUNET_NO == ret) 356 ? MHD_YES 357 : MHD_NO; 358 } 359 } 360 361 for (unsigned int retry = 0; retry<MAX_RETRIES; retry++) 362 { 363 /* Cleanup after earlier loops */ 364 { 365 struct TMH_WireMethod *wm; 366 367 while (NULL != (wm = wm_head)) 368 { 369 GNUNET_CONTAINER_DLL_remove (wm_head, 370 wm_tail, 371 wm); 372 free_wm (wm); 373 } 374 } 375 if (GNUNET_OK != 376 TALER_MERCHANTDB_start (TMH_db, 377 "PATCH /instances")) 378 { 379 GNUNET_JSON_parse_free (spec); 380 GNUNET_free (is.phone); 381 return TALER_MHD_reply_with_error (connection, 382 MHD_HTTP_INTERNAL_SERVER_ERROR, 383 TALER_EC_GENERIC_DB_START_FAILED, 384 NULL); 385 } 386 /* Check for equality of settings */ 387 if (! ( (0 == strcmp (mi->settings.name, 388 name)) && 389 ((mi->settings.email == is.email) || 390 (NULL != is.email && NULL != mi->settings.email && 391 0 == strcmp (mi->settings.email, 392 is.email))) && 393 ((mi->settings.phone == is.phone) || 394 (NULL != is.phone && NULL != mi->settings.phone && 395 0 == strcmp (mi->settings.phone, 396 is.phone))) && 397 ((mi->settings.website == is.website) || 398 (NULL != is.website && NULL != mi->settings.website && 399 0 == strcmp (mi->settings.website, 400 is.website))) && 401 ((mi->settings.logo == is.logo) || 402 (NULL != is.logo && NULL != mi->settings.logo && 403 0 == strcmp (mi->settings.logo, 404 is.logo))) && 405 (1 == json_equal (mi->settings.address, 406 is.address)) && 407 (1 == json_equal (mi->settings.jurisdiction, 408 is.jurisdiction)) && 409 (mi->settings.use_stefan == is.use_stefan) && 410 (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay, 411 ==, 412 is.default_wire_transfer_delay)) && 413 (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay, 414 ==, 415 is.default_refund_delay)) && 416 (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay, 417 ==, 418 is.default_pay_delay)) ) ) 419 { 420 is.id = mi->settings.id; 421 is.name = GNUNET_strdup (name); 422 qs = TALER_MERCHANTDB_update_instance (TMH_db, 423 &is); 424 GNUNET_free (is.name); 425 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 426 { 427 TALER_MERCHANTDB_rollback (TMH_db); 428 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 429 goto retry; 430 else 431 goto giveup; 432 } 433 } 434 qs = TALER_MERCHANTDB_commit (TMH_db); 435 retry: 436 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 437 continue; 438 break; 439 } /* for(... MAX_RETRIES) */ 440 giveup: 441 /* Update our 'settings' */ 442 GNUNET_free (mi->settings.name); 443 GNUNET_free (mi->settings.email); 444 GNUNET_free (mi->settings.phone); 445 GNUNET_free (mi->settings.website); 446 GNUNET_free (mi->settings.logo); 447 json_decref (mi->settings.address); 448 json_decref (mi->settings.jurisdiction); 449 is.id = mi->settings.id; 450 mi->settings = is; 451 mi->settings.address = json_incref (mi->settings.address); 452 mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction); 453 mi->settings.name = GNUNET_strdup (name); 454 if (NULL != is.email) 455 mi->settings.email = GNUNET_strdup (is.email); 456 mi->settings.phone = is.phone; 457 is.phone = NULL; 458 if (NULL != is.website) 459 mi->settings.website = GNUNET_strdup (is.website); 460 if (NULL != is.logo) 461 mi->settings.logo = GNUNET_strdup (is.logo); 462 463 GNUNET_JSON_parse_free (spec); 464 TMH_reload_instances (mi->settings.id); 465 return TALER_MHD_reply_static (connection, 466 MHD_HTTP_NO_CONTENT, 467 NULL, 468 NULL, 469 0); 470 } 471 472 473 enum MHD_Result 474 TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh, 475 struct MHD_Connection *connection, 476 struct TMH_HandlerContext *hc) 477 { 478 struct TMH_MerchantInstance *mi = hc->instance; 479 480 return patch_instances_ID (mi, 481 true, 482 connection, 483 hc); 484 } 485 486 487 enum MHD_Result 488 TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh, 489 struct MHD_Connection *connection, 490 struct TMH_HandlerContext *hc) 491 { 492 struct TMH_MerchantInstance *mi; 493 494 mi = TMH_lookup_instance (hc->infix); 495 if (NULL == mi) 496 { 497 return TALER_MHD_reply_with_error (connection, 498 MHD_HTTP_NOT_FOUND, 499 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 500 hc->infix); 501 } 502 if (mi->deleted) 503 { 504 return TALER_MHD_reply_with_error (connection, 505 MHD_HTTP_CONFLICT, 506 TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED, 507 hc->infix); 508 } 509 return patch_instances_ID (mi, 510 false, 511 connection, 512 hc); 513 } 514 515 516 /* end of taler-merchant-httpd_patch-management-instances-INSTANCE.c */