taler-merchant-httpd_private-patch-instances-ID.c (16693B)
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_private-patch-instances-ID.c 22 * @brief implementing PATCH /instances/$ID request handling 23 * @author Christian Grothoff 24 */ 25 #include "platform.h" 26 #include "taler-merchant-httpd_private-patch-instances-ID.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 connection the MHD connection to handle 58 * @param[in,out] hc context with further information about the request 59 * @return MHD result code 60 */ 61 static MHD_RESULT 62 patch_instances_ID (struct TMH_MerchantInstance *mi, 63 struct MHD_Connection *connection, 64 struct TMH_HandlerContext *hc) 65 { 66 struct TALER_MERCHANTDB_InstanceSettings is; 67 const char *name; 68 struct TMH_WireMethod *wm_head = NULL; 69 struct TMH_WireMethod *wm_tail = NULL; 70 bool no_transfer_delay; 71 bool no_pay_delay; 72 bool no_refund_delay; 73 struct GNUNET_JSON_Specification spec[] = { 74 GNUNET_JSON_spec_string ("name", 75 &name), 76 GNUNET_JSON_spec_mark_optional ( 77 GNUNET_JSON_spec_string ("website", 78 (const char **) &is.website), 79 NULL), 80 GNUNET_JSON_spec_mark_optional ( 81 GNUNET_JSON_spec_string ("email", 82 (const char **) &is.email), 83 NULL), 84 GNUNET_JSON_spec_mark_optional ( 85 GNUNET_JSON_spec_string ("phone_number", 86 (const char **) &is.phone), 87 NULL), 88 GNUNET_JSON_spec_mark_optional ( 89 GNUNET_JSON_spec_string ("logo", 90 (const char **) &is.logo), 91 NULL), 92 GNUNET_JSON_spec_json ("address", 93 &is.address), 94 GNUNET_JSON_spec_json ("jurisdiction", 95 &is.jurisdiction), 96 GNUNET_JSON_spec_bool ("use_stefan", 97 &is.use_stefan), 98 GNUNET_JSON_spec_mark_optional ( 99 GNUNET_JSON_spec_relative_time ("default_pay_delay", 100 &is.default_pay_delay), 101 &no_pay_delay), 102 GNUNET_JSON_spec_mark_optional ( 103 GNUNET_JSON_spec_relative_time ("default_refund_delay", 104 &is.default_refund_delay), 105 &no_refund_delay), 106 GNUNET_JSON_spec_mark_optional ( 107 GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay", 108 &is.default_wire_transfer_delay), 109 &no_transfer_delay), 110 GNUNET_JSON_spec_mark_optional ( 111 GNUNET_JSON_spec_time_rounder_interval ( 112 "default_wire_transfer_rounding_interval", 113 &is.default_wire_transfer_rounding_interval), 114 NULL), 115 GNUNET_JSON_spec_end () 116 }; 117 enum GNUNET_DB_QueryStatus qs; 118 119 GNUNET_assert (NULL != mi); 120 memset (&is, 121 0, 122 sizeof (is)); 123 { 124 enum GNUNET_GenericReturnValue res; 125 126 res = TALER_MHD_parse_json_data (connection, 127 hc->request_body, 128 spec); 129 if (GNUNET_OK != res) 130 return (GNUNET_NO == res) 131 ? MHD_YES 132 : MHD_NO; 133 } 134 if (! TMH_location_object_valid (is.address)) 135 { 136 GNUNET_break_op (0); 137 GNUNET_JSON_parse_free (spec); 138 return TALER_MHD_reply_with_error (connection, 139 MHD_HTTP_BAD_REQUEST, 140 TALER_EC_GENERIC_PARAMETER_MALFORMED, 141 "address"); 142 } 143 if ( (NULL != is.logo) && 144 (! TMH_image_data_url_valid (is.logo)) ) 145 { 146 GNUNET_break_op (0); 147 GNUNET_JSON_parse_free (spec); 148 return TALER_MHD_reply_with_error (connection, 149 MHD_HTTP_BAD_REQUEST, 150 TALER_EC_GENERIC_PARAMETER_MALFORMED, 151 "logo"); 152 } 153 154 if (! TMH_location_object_valid (is.jurisdiction)) 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 "jurisdiction"); 162 } 163 164 if (no_transfer_delay) 165 is.default_wire_transfer_delay = mi->settings.default_wire_transfer_delay; 166 if (no_pay_delay) 167 is.default_pay_delay = mi->settings.default_pay_delay; 168 if (no_refund_delay) 169 is.default_refund_delay = mi->settings.default_refund_delay; 170 if (GNUNET_TIME_relative_is_forever (is.default_pay_delay)) 171 { 172 GNUNET_break_op (0); 173 GNUNET_JSON_parse_free (spec); 174 return TALER_MHD_reply_with_error (connection, 175 MHD_HTTP_BAD_REQUEST, 176 TALER_EC_GENERIC_PARAMETER_MALFORMED, 177 "default_pay_delay"); 178 } 179 if (GNUNET_TIME_relative_is_forever (is.default_refund_delay)) 180 { 181 GNUNET_break_op (0); 182 GNUNET_JSON_parse_free (spec); 183 return TALER_MHD_reply_with_error (connection, 184 MHD_HTTP_BAD_REQUEST, 185 TALER_EC_GENERIC_PARAMETER_MALFORMED, 186 "default_refund_delay"); 187 } 188 if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay)) 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 "default_wire_transfer_delay"); 196 } 197 198 if ( (NULL != is.phone) && 199 (NULL != mi->settings.phone) && 200 0 == strcmp (mi->settings.phone, 201 is.phone) ) 202 is.phone_validated = mi->settings.phone_validated; 203 if ( (NULL != is.email) && 204 (NULL != mi->settings.email) && 205 0 == strcmp (mi->settings.email, 206 is.email) ) 207 is.email_validated = mi->settings.email_validated; 208 { 209 enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; 210 enum TEH_TanChannelSet mtc = TEH_mandatory_tan_channels; 211 212 if ( (0 != (mtc & TEH_TCS_SMS)) && 213 (NULL == is.phone) ) 214 { 215 GNUNET_break_op (0); 216 GNUNET_JSON_parse_free (spec); 217 return TALER_MHD_reply_with_error ( 218 connection, 219 MHD_HTTP_BAD_REQUEST, 220 TALER_EC_GENERIC_PARAMETER_MISSING, 221 "phone_number"); 222 } 223 if ( (0 != (mtc & TEH_TCS_EMAIL)) && 224 (NULL == is.email) ) 225 { 226 GNUNET_break_op (0); 227 GNUNET_JSON_parse_free (spec); 228 return TALER_MHD_reply_with_error ( 229 connection, 230 MHD_HTTP_BAD_REQUEST, 231 TALER_EC_GENERIC_PARAMETER_MISSING, 232 "email"); 233 } 234 if ( (is.phone_validated) && 235 (0 != (mtc & TEH_TCS_SMS)) ) 236 mtc -= TEH_TCS_SMS; 237 if ( (is.email_validated) && 238 (0 != (mtc & TEH_TCS_EMAIL)) ) 239 mtc -= TEH_TCS_EMAIL; 240 switch (mtc) 241 { 242 case TEH_TCS_NONE: 243 ret = GNUNET_OK; 244 break; 245 case TEH_TCS_SMS: 246 if (NULL == is.phone) 247 { 248 GNUNET_break_op (0); 249 GNUNET_JSON_parse_free (spec); 250 return TALER_MHD_reply_with_error ( 251 connection, 252 MHD_HTTP_BAD_REQUEST, 253 TALER_EC_GENERIC_PARAMETER_MISSING, 254 "phone_number"); 255 } 256 is.phone_validated = true; 257 /* validate new phone number, if possible require old e-mail 258 address for authorization */ 259 ret = TMH_mfa_challenges_do (hc, 260 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 261 true, 262 TALER_MERCHANT_MFA_CHANNEL_SMS, 263 is.phone, 264 0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL 265 & TEH_mandatory_tan_channels) 266 ? TALER_MERCHANT_MFA_CHANNEL_NONE 267 : TALER_MERCHANT_MFA_CHANNEL_EMAIL, 268 mi->settings.email, 269 TALER_MERCHANT_MFA_CHANNEL_NONE); 270 break; 271 case TEH_TCS_EMAIL: 272 is.email_validated = true; 273 /* validate new e-mail address, if possible require old phone 274 address for authorization */ 275 ret = TMH_mfa_challenges_do (hc, 276 TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, 277 true, 278 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 279 is.email, 280 0 == (TALER_MERCHANT_MFA_CHANNEL_SMS 281 & TEH_mandatory_tan_channels) 282 ? TALER_MERCHANT_MFA_CHANNEL_NONE 283 : TALER_MERCHANT_MFA_CHANNEL_SMS, 284 mi->settings.phone, 285 TALER_MERCHANT_MFA_CHANNEL_NONE); 286 break; 287 case TEH_TCS_EMAIL_AND_SMS: 288 is.phone_validated = true; 289 is.email_validated = true; 290 /* To change both, we require both old and both new 291 addresses to consent */ 292 ret = TMH_mfa_challenges_do (hc, 293 TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION, 294 true, 295 TALER_MERCHANT_MFA_CHANNEL_SMS, 296 mi->settings.phone, 297 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 298 mi->settings.email, 299 TALER_MERCHANT_MFA_CHANNEL_SMS, 300 is.phone, 301 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 302 is.email, 303 TALER_MERCHANT_MFA_CHANNEL_NONE); 304 break; 305 } 306 if (GNUNET_OK != ret) 307 { 308 GNUNET_JSON_parse_free (spec); 309 return (GNUNET_NO == ret) 310 ? MHD_YES 311 : MHD_NO; 312 } 313 314 } 315 316 for (unsigned int retry = 0; retry<MAX_RETRIES; retry++) 317 { 318 /* Cleanup after earlier loops */ 319 { 320 struct TMH_WireMethod *wm; 321 322 while (NULL != (wm = wm_head)) 323 { 324 GNUNET_CONTAINER_DLL_remove (wm_head, 325 wm_tail, 326 wm); 327 free_wm (wm); 328 } 329 } 330 if (GNUNET_OK != 331 TMH_db->start (TMH_db->cls, 332 "PATCH /instances")) 333 { 334 GNUNET_JSON_parse_free (spec); 335 return TALER_MHD_reply_with_error (connection, 336 MHD_HTTP_INTERNAL_SERVER_ERROR, 337 TALER_EC_GENERIC_DB_START_FAILED, 338 NULL); 339 } 340 /* Check for equality of settings */ 341 if (! ( (0 == strcmp (mi->settings.name, 342 name)) && 343 ((mi->settings.email == is.email) || 344 (NULL != is.email && NULL != mi->settings.email && 345 0 == strcmp (mi->settings.email, 346 is.email))) && 347 ((mi->settings.phone == is.phone) || 348 (NULL != is.phone && NULL != mi->settings.phone && 349 0 == strcmp (mi->settings.phone, 350 is.phone))) && 351 ((mi->settings.website == is.website) || 352 (NULL != is.website && NULL != mi->settings.website && 353 0 == strcmp (mi->settings.website, 354 is.website))) && 355 ((mi->settings.logo == is.logo) || 356 (NULL != is.logo && NULL != mi->settings.logo && 357 0 == strcmp (mi->settings.logo, 358 is.logo))) && 359 (1 == json_equal (mi->settings.address, 360 is.address)) && 361 (1 == json_equal (mi->settings.jurisdiction, 362 is.jurisdiction)) && 363 (mi->settings.use_stefan == is.use_stefan) && 364 (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay, 365 ==, 366 is.default_wire_transfer_delay)) && 367 (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay, 368 ==, 369 is.default_pay_delay)) ) ) 370 { 371 is.id = mi->settings.id; 372 is.name = GNUNET_strdup (name); 373 qs = TMH_db->update_instance (TMH_db->cls, 374 &is); 375 GNUNET_free (is.name); 376 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 377 { 378 TMH_db->rollback (TMH_db->cls); 379 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 380 goto retry; 381 else 382 goto giveup; 383 } 384 } 385 qs = TMH_db->commit (TMH_db->cls); 386 retry: 387 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 388 continue; 389 break; 390 } /* for(... MAX_RETRIES) */ 391 giveup: 392 /* Update our 'settings' */ 393 GNUNET_free (mi->settings.name); 394 GNUNET_free (mi->settings.email); 395 GNUNET_free (mi->settings.phone); 396 GNUNET_free (mi->settings.website); 397 GNUNET_free (mi->settings.logo); 398 json_decref (mi->settings.address); 399 json_decref (mi->settings.jurisdiction); 400 is.id = mi->settings.id; 401 mi->settings = is; 402 mi->settings.address = json_incref (mi->settings.address); 403 mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction); 404 mi->settings.name = GNUNET_strdup (name); 405 if (NULL != is.email) 406 mi->settings.email = GNUNET_strdup (is.email); 407 if (NULL != is.phone) 408 mi->settings.phone = GNUNET_strdup (is.phone); 409 if (NULL != is.website) 410 mi->settings.website = GNUNET_strdup (is.website); 411 if (NULL != is.logo) 412 mi->settings.logo = GNUNET_strdup (is.logo); 413 414 GNUNET_JSON_parse_free (spec); 415 TMH_reload_instances (mi->settings.id); 416 return TALER_MHD_reply_static (connection, 417 MHD_HTTP_NO_CONTENT, 418 NULL, 419 NULL, 420 0); 421 } 422 423 424 MHD_RESULT 425 TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh, 426 struct MHD_Connection *connection, 427 struct TMH_HandlerContext *hc) 428 { 429 struct TMH_MerchantInstance *mi = hc->instance; 430 431 return patch_instances_ID (mi, 432 connection, 433 hc); 434 } 435 436 437 MHD_RESULT 438 TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh, 439 struct MHD_Connection *connection, 440 struct TMH_HandlerContext *hc) 441 { 442 struct TMH_MerchantInstance *mi; 443 444 mi = TMH_lookup_instance (hc->infix); 445 if (NULL == mi) 446 { 447 return TALER_MHD_reply_with_error (connection, 448 MHD_HTTP_NOT_FOUND, 449 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 450 hc->infix); 451 } 452 if (mi->deleted) 453 { 454 return TALER_MHD_reply_with_error (connection, 455 MHD_HTTP_CONFLICT, 456 TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED, 457 hc->infix); 458 } 459 return patch_instances_ID (mi, 460 connection, 461 hc); 462 } 463 464 465 /* end of taler-merchant-httpd_private-patch-instances-ID.c */