/* This file is part of TALER (C) 2014, 2015, 2016, 2018, 2020 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_post-orders-ID-claim.c * @brief headers for POST /orders/$ID/claim handler * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-merchant-httpd_private-get-orders.h" #include "taler-merchant-httpd_post-orders-ID-claim.h" /** * How often do we retry the database transaction? */ #define MAX_RETRIES 3 /** * Run transaction to claim @a order_id for @a nonce. * * @param instance_id instance to claim order at * @param order_id order to claim * @param nonce nonce to use for the claim * @param claim_token the token that should be used to verify the claim * @param[out] contract_terms set to the resulting contract terms * (for any non-negative result; * @return transaction status code * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the order was claimed by a different * nonce (@a contract_terms set to non-NULL) * OR if the order is is unknown (@a contract_terms is NULL) * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the order was successfully claimed */ static enum GNUNET_DB_QueryStatus claim_order (const char *instance_id, const char *order_id, const char *nonce, const struct TALER_ClaimTokenP *claim_token, json_t **contract_terms) { struct TALER_ClaimTokenP order_ct; enum GNUNET_DB_QueryStatus qs; uint64_t order_serial; if (GNUNET_OK != TMH_db->start (TMH_db->cls, "claim order")) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } qs = TMH_db->lookup_contract_terms (TMH_db->cls, instance_id, order_id, contract_terms, &order_serial, NULL); if (0 > qs) { TMH_db->rollback (TMH_db->cls); return qs; } if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { /* We already have claimed contract terms for this order_id */ const char *stored_nonce; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("nonce", &stored_nonce), GNUNET_JSON_spec_end () }; TMH_db->rollback (TMH_db->cls); GNUNET_assert (NULL != *contract_terms); if (GNUNET_OK != GNUNET_JSON_parse (*contract_terms, spec, NULL, NULL)) { /* this should not be possible: contract_terms should always have a nonce! */ GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } if (0 != strcmp (stored_nonce, nonce)) { GNUNET_JSON_parse_free (spec); return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } GNUNET_JSON_parse_free (spec); return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); /* Now we need to claim the order. */ { struct TALER_MerchantPostDataHashP unused; struct GNUNET_TIME_Timestamp timestamp; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("timestamp", ×tamp), GNUNET_JSON_spec_end () }; /* see if we have this order in our table of unclaimed orders */ qs = TMH_db->lookup_order (TMH_db->cls, instance_id, order_id, &order_ct, &unused, contract_terms); if (0 >= qs) { TMH_db->rollback (TMH_db->cls); return qs; } GNUNET_assert (NULL != *contract_terms); if (GNUNET_OK != GNUNET_JSON_parse (*contract_terms, spec, NULL, NULL)) { /* this should not be possible: contract_terms should always have a timestamp! */ GNUNET_break (0); TMH_db->rollback (TMH_db->cls); return GNUNET_DB_STATUS_HARD_ERROR; } GNUNET_assert (0 == json_object_set_new (*contract_terms, "nonce", json_string (nonce))); if (0 != GNUNET_memcmp_priv (&order_ct, claim_token)) { TMH_db->rollback (TMH_db->cls); json_decref (*contract_terms); *contract_terms = NULL; return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } qs = TMH_db->insert_contract_terms (TMH_db->cls, instance_id, order_id, *contract_terms, &order_serial); if (0 >= qs) { TMH_db->rollback (TMH_db->cls); json_decref (*contract_terms); *contract_terms = NULL; return qs; } TMH_notify_order_change (TMH_lookup_instance (instance_id), TMH_OSF_CLAIMED, timestamp, order_serial); qs = TMH_db->commit (TMH_db->cls); if (0 > qs) return qs; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } } MHD_RESULT TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { const char *order_id = hc->infix; const char *nonce; enum GNUNET_DB_QueryStatus qs; json_t *contract_terms; struct TALER_ClaimTokenP claim_token = { 0 }; { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("nonce", &nonce), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("token", &claim_token), NULL), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, hc->request_body, spec); if (GNUNET_OK != res) { GNUNET_break_op (0); json_dumpf (hc->request_body, stderr, JSON_INDENT (2)); return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } } contract_terms = NULL; for (unsigned int i = 0; ipreflight (TMH_db->cls); qs = claim_order (hc->instance->settings.id, order_id, nonce, &claim_token, &contract_terms); if (GNUNET_DB_STATUS_SOFT_ERROR != qs) break; } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_COMMIT_FAILED, NULL); case GNUNET_DB_STATUS_SOFT_ERROR: return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_SOFT_FAILURE, NULL); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: if (NULL == contract_terms) return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND, order_id); /* already claimed! */ json_decref (contract_terms); return TALER_MHD_reply_with_error (connection, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED, order_id); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: GNUNET_assert (NULL != contract_terms); break; /* Good! return signature (below) */ } /* create contract signature */ { struct TALER_PrivateContractHashP hash; struct TALER_MerchantSignatureP merchant_sig; /** * Hash of the JSON contract in UTF-8 including 0-termination, * using JSON_COMPACT | JSON_SORT_KEYS */ if (GNUNET_OK != TALER_JSON_contract_hash (contract_terms, &hash)) { GNUNET_break (0); json_decref (contract_terms); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, NULL); } TALER_merchant_contract_sign (&hash, &hc->instance->merchant_priv, &merchant_sig); return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, GNUNET_JSON_pack_object_steal ("contract_terms", contract_terms), GNUNET_JSON_pack_data_auto ("sig", &merchant_sig)); } } /* end of taler-merchant-httpd_post-orders-ID-claim.c */