taler-exchange-httpd_post-coins-COIN_PUB-refund.c (11910B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2020 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_post-coins-COIN_PUB-refund.c 18 * @brief Handle refund requests; parses the POST and JSON and 19 * verifies the coin signature before handing things off 20 * to the database. 21 * @author Florian Dold 22 * @author Benedikt Mueller 23 * @author Christian Grothoff 24 */ 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_json_lib.h> 27 #include <jansson.h> 28 #include <microhttpd.h> 29 #include <pthread.h> 30 #include "taler/taler_json_lib.h" 31 #include "taler/taler_mhd_lib.h" 32 #include "taler-exchange-httpd_post-coins-COIN_PUB-refund.h" 33 #include "taler-exchange-httpd_responses.h" 34 #include "taler-exchange-httpd_get-keys.h" 35 #include "exchange-database/get_coin_denomination.h" 36 #include "exchange-database/do_refund.h" 37 38 39 /** 40 * How often do we retry after soft database errors? 41 */ 42 #define MAX_RETRIES 3 43 44 45 /** 46 * Generate successful refund confirmation message. 47 * 48 * @param connection connection to the client 49 * @param coin_pub public key of the coin 50 * @param refund details about the successful refund 51 * @return MHD result code 52 */ 53 static enum MHD_Result 54 reply_refund_success (struct MHD_Connection *connection, 55 const struct TALER_CoinSpendPublicKeyP *coin_pub, 56 const struct TALER_EXCHANGEDB_RefundListEntry *refund) 57 { 58 struct TALER_ExchangePublicKeyP pub; 59 struct TALER_ExchangeSignatureP sig; 60 enum TALER_ErrorCode ec; 61 62 if (TALER_EC_NONE != 63 (ec = TALER_exchange_online_refund_confirmation_sign ( 64 &TEH_keys_exchange_sign_, 65 &refund->h_contract_terms, 66 coin_pub, 67 &refund->merchant_pub, 68 refund->rtransaction_id, 69 &refund->refund_amount, 70 &pub, 71 &sig))) 72 { 73 return TALER_MHD_reply_with_ec (connection, 74 ec, 75 NULL); 76 } 77 return TALER_MHD_REPLY_JSON_PACK ( 78 connection, 79 MHD_HTTP_OK, 80 GNUNET_JSON_pack_data_auto ("exchange_sig", 81 &sig), 82 GNUNET_JSON_pack_data_auto ("exchange_pub", 83 &pub)); 84 } 85 86 87 /** 88 * Closure for refund_transaction(). 89 */ 90 struct RefundContext 91 { 92 /** 93 * Details about the deposit operation. 94 */ 95 const struct TALER_EXCHANGEDB_Refund *refund; 96 97 /** 98 * Deposit fee of the coin. 99 */ 100 struct TALER_Amount deposit_fee; 101 102 /** 103 * Unique ID of the coin in known_coins. 104 */ 105 uint64_t known_coin_id; 106 }; 107 108 109 /** 110 * Execute a "/refund" transaction. Returns a confirmation that the 111 * refund was successful, or a failure if we are not aware of a 112 * matching /deposit or if it is too late to do the refund. 113 * 114 * IF it returns a non-error code, the transaction logic MUST 115 * NOT queue a MHD response. IF it returns an hard error, the 116 * transaction logic MUST queue a MHD response and set @a mhd_ret. IF 117 * it returns the soft error code, the function MAY be called again to 118 * retry and MUST not queue a MHD response. 119 * 120 * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *` 121 * @param connection MHD request which triggered the transaction 122 * @param[out] mhd_ret set to MHD response status for @a connection, 123 * if transaction failed (!) 124 * @return transaction status 125 */ 126 static enum GNUNET_DB_QueryStatus 127 refund_transaction (void *cls, 128 struct MHD_Connection *connection, 129 enum MHD_Result *mhd_ret) 130 { 131 struct RefundContext *rctx = cls; 132 const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund; 133 enum GNUNET_DB_QueryStatus qs; 134 bool not_found; 135 bool refund_ok; 136 bool conflict; 137 bool gone; 138 139 /* Finally, store new refund data */ 140 qs = TALER_EXCHANGEDB_do_refund (TEH_pg, 141 refund, 142 &rctx->deposit_fee, 143 rctx->known_coin_id, 144 ¬_found, 145 &refund_ok, 146 &gone, 147 &conflict); 148 if (0 > qs) 149 { 150 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 151 *mhd_ret = TALER_MHD_reply_with_error ( 152 connection, 153 MHD_HTTP_INTERNAL_SERVER_ERROR, 154 TALER_EC_GENERIC_DB_STORE_FAILED, 155 "do refund"); 156 return qs; 157 } 158 159 if (gone) 160 { 161 *mhd_ret = TALER_MHD_reply_with_error ( 162 connection, 163 MHD_HTTP_GONE, 164 TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID, 165 NULL); 166 return GNUNET_DB_STATUS_HARD_ERROR; 167 } 168 if (conflict) 169 { 170 GNUNET_break_op (0); 171 *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( 172 connection, 173 TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT, 174 &refund->coin.denom_pub_hash, 175 &refund->coin.coin_pub); 176 return GNUNET_DB_STATUS_HARD_ERROR; 177 } 178 if (not_found) 179 { 180 *mhd_ret = TALER_MHD_reply_with_error ( 181 connection, 182 MHD_HTTP_NOT_FOUND, 183 TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND, 184 NULL); 185 return GNUNET_DB_STATUS_HARD_ERROR; 186 } 187 if (! refund_ok) 188 { 189 *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( 190 connection, 191 TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT, 192 &refund->coin.denom_pub_hash, 193 &refund->coin.coin_pub); 194 return GNUNET_DB_STATUS_HARD_ERROR; 195 } 196 return qs; 197 } 198 199 200 /** 201 * We have parsed the JSON information about the refund, do some basic 202 * sanity checks (especially that the signature on the coin is valid) 203 * and then execute the refund. Note that we need the DB to check 204 * the fee structure, so this is not done here. 205 * 206 * @param connection the MHD connection to handle 207 * @param[in,out] refund information about the refund 208 * @return MHD result code 209 */ 210 static enum MHD_Result 211 verify_and_execute_refund (struct MHD_Connection *connection, 212 struct TALER_EXCHANGEDB_Refund *refund) 213 { 214 struct RefundContext rctx = { 215 .refund = refund 216 }; 217 218 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 219 if (GNUNET_OK != 220 TALER_merchant_refund_verify (&refund->coin.coin_pub, 221 &refund->details.h_contract_terms, 222 refund->details.rtransaction_id, 223 &refund->details.refund_amount, 224 &refund->details.merchant_pub, 225 &refund->details.merchant_sig)) 226 { 227 GNUNET_break_op (0); 228 return TALER_MHD_reply_with_error ( 229 connection, 230 MHD_HTTP_FORBIDDEN, 231 TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID, 232 NULL); 233 } 234 235 /* Fetch the coin's denomination (hash) */ 236 for (unsigned int i = 0; i < MAX_RETRIES; i++) 237 { 238 enum GNUNET_DB_QueryStatus qs; 239 240 qs = TALER_EXCHANGEDB_get_coin_denomination (TEH_pg, 241 &refund->coin.coin_pub, 242 &rctx.known_coin_id, 243 &refund->coin.denom_pub_hash); 244 switch (qs) 245 { 246 case GNUNET_DB_STATUS_SOFT_ERROR: 247 if (i < MAX_RETRIES - 1) 248 continue; 249 /* otherwise: fall-through */ 250 case GNUNET_DB_STATUS_HARD_ERROR: 251 GNUNET_break (0); 252 return TALER_MHD_reply_with_error ( 253 connection, 254 MHD_HTTP_INTERNAL_SERVER_ERROR, 255 TALER_EC_GENERIC_DB_FETCH_FAILED, 256 "get_coin_denomination"); 257 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 258 { 259 enum MHD_Result res; 260 char *dhs; 261 262 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 263 dhs = GNUNET_STRINGS_data_to_string_alloc ( 264 &refund->coin.denom_pub_hash, 265 sizeof (refund->coin.denom_pub_hash)); 266 res = TALER_MHD_reply_with_error ( 267 connection, 268 MHD_HTTP_NOT_FOUND, 269 TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND, 270 dhs); 271 GNUNET_free (dhs); 272 return res; 273 } 274 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 275 break; 276 } 277 break; 278 } /* end for retries */ 279 280 { 281 /* Obtain information about the coin's denomination! */ 282 struct TEH_DenominationKey *dk; 283 enum MHD_Result mret; 284 285 dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash, 286 connection, 287 &mret); 288 if (NULL == dk) 289 { 290 /* DKI not found, but we do have a coin with this DK in our database; 291 not good... */ 292 GNUNET_break (0); 293 return mret; 294 } 295 refund->details.refund_fee = dk->meta.fees.refund; 296 rctx.deposit_fee = dk->meta.fees.deposit; 297 } 298 299 /* Finally run the actual transaction logic */ 300 { 301 enum MHD_Result mhd_ret; 302 303 if (GNUNET_OK != 304 TEH_DB_run_transaction (connection, 305 "run refund", 306 TEH_MT_REQUEST_OTHER, 307 &mhd_ret, 308 &refund_transaction, 309 &rctx)) 310 { 311 return mhd_ret; 312 } 313 } 314 return reply_refund_success (connection, 315 &refund->coin.coin_pub, 316 &refund->details); 317 } 318 319 320 /** 321 * Handle a "/coins/$COIN_PUB/refund" request. Parses the JSON, and, if 322 * successful, passes the JSON data to #verify_and_execute_refund() to further 323 * check the details of the operation specified. If everything checks out, 324 * this will ultimately lead to the refund being executed, or rejected. 325 * 326 * @param connection the MHD connection to handle 327 * @param coin_pub public key of the coin 328 * @param root uploaded JSON data 329 * @return MHD result code 330 */ 331 enum MHD_Result 332 TEH_handler_refund (struct MHD_Connection *connection, 333 const struct TALER_CoinSpendPublicKeyP *coin_pub, 334 const json_t *root) 335 { 336 struct TALER_EXCHANGEDB_Refund refund = { 337 .details.refund_fee.currency = {0} /* set to invalid, just to be sure */ 338 }; 339 struct GNUNET_JSON_Specification spec[] = { 340 TALER_JSON_spec_amount ("refund_amount", 341 TEH_currency, 342 &refund.details.refund_amount), 343 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 344 &refund.details.h_contract_terms), 345 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 346 &refund.details.merchant_pub), 347 GNUNET_JSON_spec_uint64 ("rtransaction_id", 348 &refund.details.rtransaction_id), 349 GNUNET_JSON_spec_fixed_auto ("merchant_sig", 350 &refund.details.merchant_sig), 351 GNUNET_JSON_spec_end () 352 }; 353 354 refund.coin.coin_pub = *coin_pub; 355 { 356 enum GNUNET_GenericReturnValue res; 357 358 res = TALER_MHD_parse_json_data (connection, 359 root, 360 spec); 361 if (GNUNET_SYSERR == res) 362 return MHD_NO; /* hard failure */ 363 if (GNUNET_NO == res) 364 return MHD_YES; /* failure */ 365 } 366 { 367 enum MHD_Result res; 368 369 res = verify_and_execute_refund (connection, 370 &refund); 371 GNUNET_JSON_parse_free (spec); 372 return res; 373 } 374 } 375 376 377 /* end of taler-exchange-httpd_refund.c */