Line data Source code
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_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 "platform.h"
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_json_lib.h>
28 : #include <jansson.h>
29 : #include <microhttpd.h>
30 : #include <pthread.h>
31 : #include "taler_json_lib.h"
32 : #include "taler_mhd_lib.h"
33 : #include "taler-exchange-httpd_refund.h"
34 : #include "taler-exchange-httpd_responses.h"
35 : #include "taler-exchange-httpd_keys.h"
36 :
37 :
38 : /**
39 : * Generate successful refund confirmation message.
40 : *
41 : * @param connection connection to the client
42 : * @param coin_pub public key of the coin
43 : * @param refund details about the successful refund
44 : * @return MHD result code
45 : */
46 : static MHD_RESULT
47 0 : reply_refund_success (struct MHD_Connection *connection,
48 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
49 : const struct TALER_EXCHANGEDB_RefundListEntry *refund)
50 : {
51 : struct TALER_ExchangePublicKeyP pub;
52 : struct TALER_ExchangeSignatureP sig;
53 : enum TALER_ErrorCode ec;
54 :
55 0 : if (TALER_EC_NONE !=
56 0 : (ec = TALER_exchange_online_refund_confirmation_sign (
57 : &TEH_keys_exchange_sign_,
58 : &refund->h_contract_terms,
59 : coin_pub,
60 : &refund->merchant_pub,
61 : refund->rtransaction_id,
62 : &refund->refund_amount,
63 : &pub,
64 : &sig)))
65 : {
66 0 : return TALER_MHD_reply_with_ec (connection,
67 : ec,
68 : NULL);
69 : }
70 0 : return TALER_MHD_REPLY_JSON_PACK (
71 : connection,
72 : MHD_HTTP_OK,
73 : GNUNET_JSON_pack_data_auto ("exchange_sig",
74 : &sig),
75 : GNUNET_JSON_pack_data_auto ("exchange_pub",
76 : &pub));
77 : }
78 :
79 :
80 : /**
81 : * Closure for refund_transaction().
82 : */
83 : struct RefundContext
84 : {
85 : /**
86 : * Details about the deposit operation.
87 : */
88 : const struct TALER_EXCHANGEDB_Refund *refund;
89 :
90 : /**
91 : * Deposit fee of the coin.
92 : */
93 : struct TALER_Amount deposit_fee;
94 :
95 : /**
96 : * Unique ID of the coin in known_coins.
97 : */
98 : uint64_t known_coin_id;
99 : };
100 :
101 :
102 : /**
103 : * Execute a "/refund" transaction. Returns a confirmation that the
104 : * refund was successful, or a failure if we are not aware of a
105 : * matching /deposit or if it is too late to do the refund.
106 : *
107 : * IF it returns a non-error code, the transaction logic MUST
108 : * NOT queue a MHD response. IF it returns an hard error, the
109 : * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
110 : * it returns the soft error code, the function MAY be called again to
111 : * retry and MUST not queue a MHD response.
112 : *
113 : * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
114 : * @param connection MHD request which triggered the transaction
115 : * @param[out] mhd_ret set to MHD response status for @a connection,
116 : * if transaction failed (!)
117 : * @return transaction status
118 : */
119 : static enum GNUNET_DB_QueryStatus
120 0 : refund_transaction (void *cls,
121 : struct MHD_Connection *connection,
122 : MHD_RESULT *mhd_ret)
123 : {
124 0 : struct RefundContext *rctx = cls;
125 0 : const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund;
126 : enum GNUNET_DB_QueryStatus qs;
127 : bool not_found;
128 : bool refund_ok;
129 : bool conflict;
130 : bool gone;
131 :
132 : /* Finally, store new refund data */
133 0 : qs = TEH_plugin->do_refund (TEH_plugin->cls,
134 : refund,
135 0 : &rctx->deposit_fee,
136 : rctx->known_coin_id,
137 : ¬_found,
138 : &refund_ok,
139 : &gone,
140 : &conflict);
141 0 : if (0 > qs)
142 : {
143 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
144 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
145 : MHD_HTTP_INTERNAL_SERVER_ERROR,
146 : TALER_EC_GENERIC_DB_FETCH_FAILED,
147 : "do refund");
148 0 : return qs;
149 : }
150 :
151 0 : if (gone)
152 : {
153 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
154 : MHD_HTTP_GONE,
155 : TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID,
156 : NULL);
157 0 : return GNUNET_DB_STATUS_HARD_ERROR;
158 : }
159 0 : if (conflict)
160 : {
161 0 : *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
162 : connection,
163 : TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
164 : &refund->coin.denom_pub_hash,
165 : &refund->coin.coin_pub);
166 0 : return GNUNET_DB_STATUS_HARD_ERROR;
167 : }
168 0 : if (not_found)
169 : {
170 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
171 : MHD_HTTP_NOT_FOUND,
172 : TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND,
173 : NULL);
174 0 : return GNUNET_DB_STATUS_HARD_ERROR;
175 : }
176 0 : if (! refund_ok)
177 : {
178 0 : *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
179 : connection,
180 : TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT,
181 : &refund->coin.denom_pub_hash,
182 : &refund->coin.coin_pub);
183 0 : return GNUNET_DB_STATUS_HARD_ERROR;
184 : }
185 0 : return qs;
186 : }
187 :
188 :
189 : /**
190 : * We have parsed the JSON information about the refund, do some basic
191 : * sanity checks (especially that the signature on the coin is valid)
192 : * and then execute the refund. Note that we need the DB to check
193 : * the fee structure, so this is not done here.
194 : *
195 : * @param connection the MHD connection to handle
196 : * @param[in,out] refund information about the refund
197 : * @return MHD result code
198 : */
199 : static MHD_RESULT
200 0 : verify_and_execute_refund (struct MHD_Connection *connection,
201 : struct TALER_EXCHANGEDB_Refund *refund)
202 : {
203 0 : struct RefundContext rctx = {
204 : .refund = refund
205 : };
206 :
207 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
208 0 : if (GNUNET_OK !=
209 0 : TALER_merchant_refund_verify (&refund->coin.coin_pub,
210 0 : &refund->details.h_contract_terms,
211 : refund->details.rtransaction_id,
212 0 : &refund->details.refund_amount,
213 0 : &refund->details.merchant_pub,
214 0 : &refund->details.merchant_sig))
215 : {
216 0 : TALER_LOG_WARNING ("Invalid signature on refund request\n");
217 0 : return TALER_MHD_reply_with_error (connection,
218 : MHD_HTTP_FORBIDDEN,
219 : TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID,
220 : NULL);
221 : }
222 :
223 : /* Fetch the coin's denomination (hash) */
224 : {
225 : enum GNUNET_DB_QueryStatus qs;
226 :
227 0 : qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls,
228 0 : &refund->coin.coin_pub,
229 : &rctx.known_coin_id,
230 : &refund->coin.denom_pub_hash);
231 0 : if (0 > qs)
232 : {
233 : MHD_RESULT res;
234 : char *dhs;
235 :
236 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
237 0 : dhs = GNUNET_STRINGS_data_to_string_alloc (
238 0 : &refund->coin.denom_pub_hash,
239 : sizeof (refund->coin.denom_pub_hash));
240 0 : res = TALER_MHD_reply_with_error (connection,
241 : MHD_HTTP_NOT_FOUND,
242 : TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND,
243 : dhs);
244 0 : GNUNET_free (dhs);
245 0 : return res;
246 : }
247 : }
248 :
249 : {
250 : /* Obtain information about the coin's denomination! */
251 : struct TEH_DenominationKey *dk;
252 : MHD_RESULT mret;
253 :
254 0 : dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash,
255 : connection,
256 : &mret);
257 0 : if (NULL == dk)
258 : {
259 : /* DKI not found, but we do have a coin with this DK in our database;
260 : not good... */
261 0 : GNUNET_break (0);
262 0 : return mret;
263 : }
264 0 : refund->details.refund_fee = dk->meta.fees.refund;
265 0 : rctx.deposit_fee = dk->meta.fees.deposit;
266 : }
267 :
268 : /* Finally run the actual transaction logic */
269 : {
270 : MHD_RESULT mhd_ret;
271 :
272 0 : if (GNUNET_OK !=
273 0 : TEH_DB_run_transaction (connection,
274 : "run refund",
275 : TEH_MT_REQUEST_OTHER,
276 : &mhd_ret,
277 : &refund_transaction,
278 : &rctx))
279 : {
280 0 : return mhd_ret;
281 : }
282 : }
283 0 : return reply_refund_success (connection,
284 0 : &refund->coin.coin_pub,
285 0 : &refund->details);
286 : }
287 :
288 :
289 : /**
290 : * Handle a "/coins/$COIN_PUB/refund" request. Parses the JSON, and, if
291 : * successful, passes the JSON data to #verify_and_execute_refund() to further
292 : * check the details of the operation specified. If everything checks out,
293 : * this will ultimately lead to the refund being executed, or rejected.
294 : *
295 : * @param connection the MHD connection to handle
296 : * @param coin_pub public key of the coin
297 : * @param root uploaded JSON data
298 : * @return MHD result code
299 : */
300 : MHD_RESULT
301 0 : TEH_handler_refund (struct MHD_Connection *connection,
302 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
303 : const json_t *root)
304 : {
305 0 : struct TALER_EXCHANGEDB_Refund refund = {
306 : .details.refund_fee.currency = {0} /* set to invalid, just to be sure */
307 : };
308 : struct GNUNET_JSON_Specification spec[] = {
309 0 : TALER_JSON_spec_amount ("refund_amount",
310 : TEH_currency,
311 : &refund.details.refund_amount),
312 0 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
313 : &refund.details.h_contract_terms),
314 0 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
315 : &refund.details.merchant_pub),
316 0 : GNUNET_JSON_spec_uint64 ("rtransaction_id",
317 : &refund.details.rtransaction_id),
318 0 : GNUNET_JSON_spec_fixed_auto ("merchant_sig",
319 : &refund.details.merchant_sig),
320 0 : GNUNET_JSON_spec_end ()
321 : };
322 :
323 0 : refund.coin.coin_pub = *coin_pub;
324 : {
325 : enum GNUNET_GenericReturnValue res;
326 :
327 0 : res = TALER_MHD_parse_json_data (connection,
328 : root,
329 : spec);
330 0 : if (GNUNET_SYSERR == res)
331 0 : return MHD_NO; /* hard failure */
332 0 : if (GNUNET_NO == res)
333 0 : return MHD_YES; /* failure */
334 : }
335 : {
336 : MHD_RESULT res;
337 :
338 0 : res = verify_and_execute_refund (connection,
339 : &refund);
340 0 : GNUNET_JSON_parse_free (spec);
341 0 : return res;
342 : }
343 : }
344 :
345 :
346 : /* end of taler-exchange-httpd_refund.c */
|