/* This file is part of TALER (C) 2020-2021 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-merchant-httpd_private-patch-instances-ID.c * @brief implementing PATCH /instances/$ID request handling * @author Christian Grothoff */ #include "platform.h" #include "taler-merchant-httpd_private-patch-instances-ID.h" #include "taler-merchant-httpd_helper.h" #include /** * How often do we retry the simple INSERT database transaction? */ #define MAX_RETRIES 3 /** * Free memory used by @a wm * * @param wm wire method to free */ static void free_wm (struct TMH_WireMethod *wm) { json_decref (wm->j_wire); GNUNET_free (wm->wire_method); GNUNET_free (wm); } /** * PATCH configuration of an existing instance, given its configuration. * * @param mi instance to patch * @param connection the MHD connection to handle * @param[in,out] hc context with further information about the request * @return MHD result code */ static MHD_RESULT patch_instances_ID (struct TMH_MerchantInstance *mi, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct TALER_MERCHANTDB_InstanceSettings is; json_t *payto_uris; const char *name; struct TMH_WireMethod *wm_head = NULL; struct TMH_WireMethod *wm_tail = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("payto_uris", &payto_uris), GNUNET_JSON_spec_string ("name", &name), GNUNET_JSON_spec_json ("address", &is.address), GNUNET_JSON_spec_json ("jurisdiction", &is.jurisdiction), TALER_JSON_spec_amount ("default_max_wire_fee", TMH_currency, &is.default_max_wire_fee), GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization", &is.default_wire_fee_amortization), TALER_JSON_spec_amount ("default_max_deposit_fee", TMH_currency, &is.default_max_deposit_fee), TALER_JSON_spec_relative_time ("default_wire_transfer_delay", &is.default_wire_transfer_delay), TALER_JSON_spec_relative_time ("default_pay_delay", &is.default_pay_delay), GNUNET_JSON_spec_end () }; enum GNUNET_DB_QueryStatus qs; bool committed = false; GNUNET_assert (NULL != mi); memset (&is, 0, sizeof (is)); { enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, hc->request_body, spec); if (GNUNET_OK != res) return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } if (! TMH_location_object_valid (is.address)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "address"); } if (! TMH_location_object_valid (is.jurisdiction)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "jurisdiction"); } if (! TMH_payto_uri_array_valid (payto_uris)) return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PAYTO_URI_MALFORMED, NULL); for (unsigned int i = 0; istart (TMH_db->cls, "PATCH /instances")) { GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_START_FAILED, NULL); } /* Check for equality of settings */ if (! ( (0 == strcmp (mi->settings.name, name)) && (1 == json_equal (mi->settings.address, is.address)) && (1 == json_equal (mi->settings.jurisdiction, is.jurisdiction)) && (GNUNET_YES == TALER_amount_cmp_currency ( &mi->settings.default_max_deposit_fee, &is.default_max_deposit_fee)) && (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee, &is.default_max_deposit_fee)) && (GNUNET_YES == TALER_amount_cmp_currency ( &mi->settings.default_max_wire_fee, &is.default_max_wire_fee)) && (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee, &is.default_max_wire_fee)) && (mi->settings.default_wire_fee_amortization == is.default_wire_fee_amortization) && (mi->settings.default_wire_transfer_delay.rel_value_us == is.default_wire_transfer_delay.rel_value_us) && (mi->settings.default_pay_delay.rel_value_us == is.default_pay_delay.rel_value_us) ) ) { is.id = mi->settings.id; is.name = GNUNET_strdup (name); qs = TMH_db->update_instance (TMH_db->cls, &is); GNUNET_free (is.name); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto retry; else goto giveup; } } /* Check for changes in accounts */ { unsigned int len = json_array_size (payto_uris); struct TMH_WireMethod *matches[GNUNET_NZL (len)]; bool matched; memset (matches, 0, sizeof (matches)); for (struct TMH_WireMethod *wm = mi->wm_head; NULL != wm; wm = wm->next) { const char *uri = json_string_value (json_object_get (wm->j_wire, "payto_uri")); GNUNET_assert (NULL != uri); matched = false; for (unsigned int i = 0; iactive) ) { /* Account was REMOVED */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Existing account `%s' not found, inactivating it.\n", uri); wm->deleting = true; qs = TMH_db->inactivate_account (TMH_db->cls, mi->settings.id, &wm->h_wire); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto retry; else goto giveup; } } } /* Find _new_ accounts */ for (unsigned int i = 0; iactive) { qs = TMH_db->activate_account (TMH_db->cls, mi->settings.id, &wm->h_wire); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto retry; else goto giveup; } } wm->enabling = true; continue; } ad.payto_uri = json_string_value (json_array_get (payto_uris, i)); GNUNET_assert (NULL != ad.payto_uri); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Adding NEW account `%s'\n", ad.payto_uri); wm = TMH_setup_wire_account (ad.payto_uri); GNUNET_CONTAINER_DLL_insert (wm_head, wm_tail, wm); ad.h_wire = wm->h_wire; ad.active = true; qs = TMH_db->insert_account (TMH_db->cls, mi->settings.id, &ad); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto retry; else goto giveup; } } } qs = TMH_db->commit (TMH_db->cls); retry: if (GNUNET_DB_STATUS_SOFT_ERROR == qs) continue; if (qs >= 0) committed = true; break; } /* for(... MAX_RETRIES) */ giveup: /* Deactivate existing wire methods that were removed above */ for (struct TMH_WireMethod *wm = mi->wm_head; NULL != wm; wm = wm->next) { /* We did not flip the 'active' bits earlier because the DB transaction could still fail. Now it is time to update our runtime state. */ GNUNET_assert (! (wm->deleting & wm->enabling)); if (committed) { if (wm->deleting) wm->active = false; if (wm->enabling) wm->active = true; } wm->deleting = false; wm->enabling = false; } if (! committed) { struct TMH_WireMethod *wm; while (NULL != (wm = wm_head)) { GNUNET_CONTAINER_DLL_remove (wm_head, wm_tail, wm); free_wm (wm); } GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_COMMIT_FAILED, NULL); } /* Update our 'settings' */ GNUNET_free (mi->settings.name); json_decref (mi->settings.address); json_decref (mi->settings.jurisdiction); is.id = mi->settings.id; mi->settings = is; mi->settings.address = json_incref (mi->settings.address); mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction); mi->settings.name = GNUNET_strdup (name); /* Add 'new' wire methods to our list */ { struct TMH_WireMethod *wm; /* Note: this _could_ be done more efficiently if someone wrote a GNUNET_CONTAINER_DLL_merge()... */ while (NULL != (wm = wm_head)) { GNUNET_CONTAINER_DLL_remove (wm_head, wm_tail, wm); GNUNET_CONTAINER_DLL_insert (mi->wm_head, mi->wm_tail, wm); } } GNUNET_JSON_parse_free (spec); TMH_reload_instances (mi->settings.id); return TALER_MHD_reply_static (connection, MHD_HTTP_NO_CONTENT, NULL, NULL, 0); } MHD_RESULT TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct TMH_MerchantInstance *mi = hc->instance; return patch_instances_ID (mi, connection, hc); } MHD_RESULT TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct TMH_MerchantInstance *mi; mi = TMH_lookup_instance (hc->infix); if (NULL == mi) { return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, hc->infix); } if (mi->deleted) { return TALER_MHD_reply_with_error (connection, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED, hc->infix); } return patch_instances_ID (mi, connection, hc); } /* end of taler-merchant-httpd_private-patch-instances-ID.c */