taler-merchant-httpd_post-orders-ID-claim.c (10210B)
1 /* 2 This file is part of TALER 3 (C) 2014, 2015, 2016, 2018, 2020 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_post-orders-ID-claim.c 22 * @brief headers for POST /orders/$ID/claim handler 23 * @author Marcello Stanisci 24 * @author Christian Grothoff 25 */ 26 #include "platform.h" 27 #include <jansson.h> 28 #include <taler/taler_signatures.h> 29 #include <taler/taler_json_lib.h> 30 #include "taler-merchant-httpd_private-get-orders.h" 31 #include "taler-merchant-httpd_post-orders-ID-claim.h" 32 33 34 /** 35 * How often do we retry the database transaction? 36 */ 37 #define MAX_RETRIES 3 38 39 40 /** 41 * Run transaction to claim @a order_id for @a nonce. 42 * 43 * @param instance_id instance to claim order at 44 * @param order_id order to claim 45 * @param nonce nonce to use for the claim 46 * @param claim_token the token that should be used to verify the claim 47 * @param[out] contract_terms set to the resulting contract terms 48 * (for any non-negative result; 49 * @return transaction status code 50 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the order was claimed by a different 51 * nonce (@a contract_terms set to non-NULL) 52 * OR if the order is is unknown (@a contract_terms is NULL) 53 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the order was successfully claimed 54 */ 55 static enum GNUNET_DB_QueryStatus 56 claim_order (const char *instance_id, 57 const char *order_id, 58 const char *nonce, 59 const struct TALER_ClaimTokenP *claim_token, 60 json_t **contract_terms) 61 { 62 struct TALER_ClaimTokenP order_ct; 63 enum GNUNET_DB_QueryStatus qs; 64 uint64_t order_serial; 65 66 if (GNUNET_OK != 67 TMH_db->start (TMH_db->cls, 68 "claim order")) 69 { 70 GNUNET_break (0); 71 return GNUNET_DB_STATUS_HARD_ERROR; 72 } 73 qs = TMH_db->lookup_contract_terms (TMH_db->cls, 74 instance_id, 75 order_id, 76 contract_terms, 77 &order_serial, 78 NULL); 79 if (0 > qs) 80 { 81 TMH_db->rollback (TMH_db->cls); 82 return qs; 83 } 84 85 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 86 { 87 /* We already have claimed contract terms for this order_id */ 88 const char *stored_nonce; 89 struct GNUNET_JSON_Specification spec[] = { 90 GNUNET_JSON_spec_string ("nonce", 91 &stored_nonce), 92 GNUNET_JSON_spec_end () 93 }; 94 95 TMH_db->rollback (TMH_db->cls); 96 GNUNET_assert (NULL != *contract_terms); 97 98 if (GNUNET_OK != 99 GNUNET_JSON_parse (*contract_terms, 100 spec, 101 NULL, 102 NULL)) 103 { 104 /* this should not be possible: contract_terms should always 105 have a nonce! */ 106 GNUNET_break (0); 107 return GNUNET_DB_STATUS_HARD_ERROR; 108 } 109 110 if (0 != strcmp (stored_nonce, 111 nonce)) 112 { 113 GNUNET_JSON_parse_free (spec); 114 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 115 } 116 GNUNET_JSON_parse_free (spec); 117 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 118 } 119 120 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 121 122 /* Now we need to claim the order. */ 123 { 124 struct TALER_MerchantPostDataHashP unused; 125 struct GNUNET_TIME_Timestamp timestamp; 126 struct GNUNET_JSON_Specification spec[] = { 127 GNUNET_JSON_spec_timestamp ("timestamp", 128 ×tamp), 129 GNUNET_JSON_spec_end () 130 }; 131 132 /* see if we have this order in our table of unclaimed orders */ 133 qs = TMH_db->lookup_order (TMH_db->cls, 134 instance_id, 135 order_id, 136 &order_ct, 137 &unused, 138 contract_terms); 139 if (0 >= qs) 140 { 141 TMH_db->rollback (TMH_db->cls); 142 return qs; 143 } 144 GNUNET_assert (NULL != *contract_terms); 145 if (GNUNET_OK != 146 GNUNET_JSON_parse (*contract_terms, 147 spec, 148 NULL, 149 NULL)) 150 { 151 /* this should not be possible: contract_terms should always 152 have a timestamp! */ 153 GNUNET_break (0); 154 TMH_db->rollback (TMH_db->cls); 155 return GNUNET_DB_STATUS_HARD_ERROR; 156 } 157 158 GNUNET_assert (0 == 159 json_object_set_new (*contract_terms, 160 "nonce", 161 json_string (nonce))); 162 if (0 != GNUNET_memcmp_priv (&order_ct, 163 claim_token)) 164 { 165 TMH_db->rollback (TMH_db->cls); 166 json_decref (*contract_terms); 167 *contract_terms = NULL; 168 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 169 } 170 qs = TMH_db->insert_contract_terms (TMH_db->cls, 171 instance_id, 172 order_id, 173 *contract_terms, 174 &order_serial); 175 if (0 >= qs) 176 { 177 TMH_db->rollback (TMH_db->cls); 178 json_decref (*contract_terms); 179 *contract_terms = NULL; 180 return qs; 181 } 182 TMH_notify_order_change (TMH_lookup_instance (instance_id), 183 TMH_OSF_CLAIMED, 184 timestamp, 185 order_serial); 186 qs = TMH_db->commit (TMH_db->cls); 187 if (0 > qs) 188 return qs; 189 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 190 } 191 } 192 193 194 MHD_RESULT 195 TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh, 196 struct MHD_Connection *connection, 197 struct TMH_HandlerContext *hc) 198 { 199 const char *order_id = hc->infix; 200 const char *nonce; 201 enum GNUNET_DB_QueryStatus qs; 202 json_t *contract_terms; 203 struct TALER_ClaimTokenP claim_token = { 0 }; 204 205 { 206 struct GNUNET_JSON_Specification spec[] = { 207 GNUNET_JSON_spec_string ("nonce", 208 &nonce), 209 GNUNET_JSON_spec_mark_optional ( 210 GNUNET_JSON_spec_fixed_auto ("token", 211 &claim_token), 212 NULL), 213 GNUNET_JSON_spec_end () 214 }; 215 enum GNUNET_GenericReturnValue res; 216 217 res = TALER_MHD_parse_json_data (connection, 218 hc->request_body, 219 spec); 220 if (GNUNET_OK != res) 221 { 222 GNUNET_break_op (0); 223 json_dumpf (hc->request_body, 224 stderr, 225 JSON_INDENT (2)); 226 return (GNUNET_NO == res) 227 ? MHD_YES 228 : MHD_NO; 229 } 230 } 231 contract_terms = NULL; 232 for (unsigned int i = 0; i<MAX_RETRIES; i++) 233 { 234 TMH_db->preflight (TMH_db->cls); 235 qs = claim_order (hc->instance->settings.id, 236 order_id, 237 nonce, 238 &claim_token, 239 &contract_terms); 240 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 241 break; 242 } 243 switch (qs) 244 { 245 case GNUNET_DB_STATUS_HARD_ERROR: 246 return TALER_MHD_reply_with_error (connection, 247 MHD_HTTP_INTERNAL_SERVER_ERROR, 248 TALER_EC_GENERIC_DB_COMMIT_FAILED, 249 NULL); 250 case GNUNET_DB_STATUS_SOFT_ERROR: 251 return TALER_MHD_reply_with_error (connection, 252 MHD_HTTP_INTERNAL_SERVER_ERROR, 253 TALER_EC_GENERIC_DB_SOFT_FAILURE, 254 NULL); 255 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 256 if (NULL == contract_terms) 257 return TALER_MHD_reply_with_error (connection, 258 MHD_HTTP_NOT_FOUND, 259 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND, 260 order_id); 261 /* already claimed! */ 262 json_decref (contract_terms); 263 return TALER_MHD_reply_with_error (connection, 264 MHD_HTTP_CONFLICT, 265 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED, 266 order_id); 267 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 268 GNUNET_assert (NULL != contract_terms); 269 break; /* Good! return signature (below) */ 270 } 271 272 /* create contract signature */ 273 { 274 struct TALER_PrivateContractHashP hash; 275 struct TALER_MerchantSignatureP merchant_sig; 276 277 /** 278 * Hash of the JSON contract in UTF-8 including 0-termination, 279 * using JSON_COMPACT | JSON_SORT_KEYS 280 */ 281 282 if (GNUNET_OK != 283 TALER_JSON_contract_hash (contract_terms, 284 &hash)) 285 { 286 GNUNET_break (0); 287 json_decref (contract_terms); 288 return TALER_MHD_reply_with_error (connection, 289 MHD_HTTP_INTERNAL_SERVER_ERROR, 290 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 291 NULL); 292 } 293 294 TALER_merchant_contract_sign (&hash, 295 &hc->instance->merchant_priv, 296 &merchant_sig); 297 return TALER_MHD_REPLY_JSON_PACK ( 298 connection, 299 MHD_HTTP_OK, 300 GNUNET_JSON_pack_object_steal ("contract_terms", 301 contract_terms), 302 GNUNET_JSON_pack_data_auto ("sig", 303 &merchant_sig)); 304 } 305 } 306 307 308 /* end of taler-merchant-httpd_post-orders-ID-claim.c */