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