Line data Source code
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 src/backend/taler-merchant-httpd_post-orders-ORDER_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_dbevents.h>
30 : #include <taler/taler_json_lib.h>
31 : #include "taler-merchant-httpd_get-private-orders.h"
32 : #include "taler-merchant-httpd_post-orders-ORDER_ID-claim.h"
33 : #include "merchant-database/insert_contract_terms.h"
34 : #include "merchant-database/get_contract_terms.h"
35 : #include "merchant-database/get_order.h"
36 : #include "merchant-database/event_notify.h"
37 : #include "merchant-database/preflight.h"
38 : #include "merchant-database/start.h"
39 :
40 :
41 : /**
42 : * How often do we retry the database transaction?
43 : */
44 : #define MAX_RETRIES 3
45 :
46 :
47 : enum GNUNET_DB_QueryStatus
48 105 : TMH_claim_order (struct TMH_HandlerContext *hc,
49 : const char *order_id,
50 : const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
51 : const struct TALER_ClaimTokenP *claim_token,
52 : json_t **contract_terms)
53 : {
54 105 : const char *instance_id = hc->instance->settings.id;
55 : struct TALER_ClaimTokenP order_ct;
56 : enum GNUNET_DB_QueryStatus qs;
57 : uint64_t order_serial;
58 :
59 105 : if (GNUNET_OK !=
60 105 : TALER_MERCHANTDB_start (TMH_db,
61 : "claim order"))
62 : {
63 0 : GNUNET_break (0);
64 0 : return GNUNET_DB_STATUS_HARD_ERROR;
65 : }
66 105 : qs = TALER_MERCHANTDB_get_contract_terms (TMH_db,
67 : instance_id,
68 : order_id,
69 : contract_terms,
70 : &order_serial,
71 : NULL);
72 105 : if (0 > qs)
73 : {
74 0 : TALER_MERCHANTDB_rollback (TMH_db);
75 0 : return qs;
76 : }
77 :
78 105 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
79 : {
80 : /* We already have claimed contract terms for this order_id */
81 : struct GNUNET_CRYPTO_EddsaPublicKey stored_nonce;
82 : struct GNUNET_JSON_Specification spec[] = {
83 19 : GNUNET_JSON_spec_fixed_auto ("nonce",
84 : &stored_nonce),
85 19 : GNUNET_JSON_spec_end ()
86 : };
87 :
88 19 : TALER_MERCHANTDB_rollback (TMH_db);
89 19 : GNUNET_assert (NULL != *contract_terms);
90 :
91 19 : if (GNUNET_OK !=
92 19 : GNUNET_JSON_parse (*contract_terms,
93 : spec,
94 : NULL,
95 : NULL))
96 : {
97 : /* this should not be possible: contract_terms should always
98 : have a nonce! */
99 0 : GNUNET_break (0);
100 0 : json_decref (*contract_terms);
101 0 : *contract_terms = NULL;
102 0 : return GNUNET_DB_STATUS_HARD_ERROR;
103 : }
104 :
105 19 : if (0 !=
106 19 : GNUNET_memcmp (&stored_nonce,
107 : nonce))
108 : {
109 4 : GNUNET_JSON_parse_free (spec);
110 4 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
111 : }
112 15 : GNUNET_JSON_parse_free (spec);
113 15 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
114 : }
115 :
116 86 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
117 :
118 : /* Now we need to claim the order. */
119 : {
120 : struct TALER_MerchantPostDataHashP unused;
121 : struct GNUNET_TIME_Timestamp timestamp;
122 : struct GNUNET_JSON_Specification spec[] = {
123 86 : GNUNET_JSON_spec_timestamp ("timestamp",
124 : ×tamp),
125 86 : GNUNET_JSON_spec_end ()
126 : };
127 :
128 : /* see if we have this order in our table of unclaimed orders */
129 86 : qs = TALER_MERCHANTDB_get_order (TMH_db,
130 : instance_id,
131 : order_id,
132 : &order_ct,
133 : &unused,
134 : contract_terms);
135 86 : if (0 >= qs)
136 : {
137 2 : TALER_MERCHANTDB_rollback (TMH_db);
138 2 : return qs;
139 : }
140 84 : GNUNET_assert (NULL != *contract_terms);
141 84 : if (GNUNET_OK !=
142 84 : GNUNET_JSON_parse (*contract_terms,
143 : spec,
144 : NULL,
145 : NULL))
146 : {
147 : /* this should not be possible: contract_terms should always
148 : have a timestamp! */
149 0 : GNUNET_break (0);
150 0 : TALER_MERCHANTDB_rollback (TMH_db);
151 0 : json_decref (*contract_terms);
152 0 : *contract_terms = NULL;
153 0 : return GNUNET_DB_STATUS_HARD_ERROR;
154 : }
155 :
156 84 : GNUNET_assert (0 ==
157 : json_object_set_new (
158 : *contract_terms,
159 : "nonce",
160 : GNUNET_JSON_from_data_auto (nonce)));
161 84 : if (0 != GNUNET_memcmp_priv (&order_ct,
162 : claim_token))
163 : {
164 0 : TALER_MERCHANTDB_rollback (TMH_db);
165 0 : json_decref (*contract_terms);
166 0 : *contract_terms = NULL;
167 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
168 : }
169 84 : qs = TALER_MERCHANTDB_insert_contract_terms (TMH_db,
170 : instance_id,
171 : order_id,
172 : *contract_terms,
173 : &order_serial);
174 84 : if (0 >= qs)
175 : {
176 0 : TALER_MERCHANTDB_rollback (TMH_db);
177 0 : json_decref (*contract_terms);
178 0 : *contract_terms = NULL;
179 0 : return qs;
180 : }
181 : // FIXME: unify notifications? or do we need both?
182 84 : TMH_notify_order_change (TMH_lookup_instance (instance_id),
183 : TMH_OSF_CLAIMED,
184 : timestamp,
185 : order_serial);
186 : {
187 84 : struct TMH_OrderPayEventP pay_eh = {
188 84 : .header.size = htons (sizeof (pay_eh)),
189 84 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
190 84 : .merchant_pub = hc->instance->merchant_pub
191 : };
192 :
193 84 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
194 : "Notifying clients about status change of order %s\n",
195 : order_id);
196 84 : GNUNET_CRYPTO_hash (order_id,
197 : strlen (order_id),
198 : &pay_eh.h_order_id);
199 84 : TALER_MERCHANTDB_event_notify (TMH_db,
200 : &pay_eh.header,
201 : NULL,
202 : 0);
203 : }
204 84 : qs = TALER_MERCHANTDB_commit (TMH_db);
205 84 : if (0 > qs)
206 0 : return qs;
207 84 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
208 : }
209 : }
210 :
211 :
212 : enum MHD_Result
213 95 : TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
214 : struct MHD_Connection *connection,
215 : struct TMH_HandlerContext *hc)
216 : {
217 95 : const char *order_id = hc->infix;
218 : struct GNUNET_CRYPTO_EddsaPublicKey nonce;
219 : enum GNUNET_DB_QueryStatus qs;
220 : json_t *contract_terms;
221 95 : struct TALER_ClaimTokenP claim_token = { 0 };
222 :
223 : {
224 : struct GNUNET_JSON_Specification spec[] = {
225 95 : GNUNET_JSON_spec_fixed_auto ("nonce",
226 : &nonce),
227 95 : GNUNET_JSON_spec_mark_optional (
228 : GNUNET_JSON_spec_fixed_auto ("token",
229 : &claim_token),
230 : NULL),
231 95 : GNUNET_JSON_spec_end ()
232 : };
233 : enum GNUNET_GenericReturnValue res;
234 :
235 95 : res = TALER_MHD_parse_json_data (connection,
236 95 : hc->request_body,
237 : spec);
238 95 : if (GNUNET_OK != res)
239 : {
240 0 : GNUNET_break_op (0);
241 0 : json_dumpf (hc->request_body,
242 : stderr,
243 : JSON_INDENT (2));
244 : return (GNUNET_NO == res)
245 : ? MHD_YES
246 0 : : MHD_NO;
247 : }
248 : }
249 95 : contract_terms = NULL;
250 95 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
251 : {
252 95 : TALER_MERCHANTDB_preflight (TMH_db);
253 95 : qs = TMH_claim_order (hc,
254 : order_id,
255 : &nonce,
256 : &claim_token,
257 : &contract_terms);
258 95 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
259 95 : break;
260 : }
261 95 : switch (qs)
262 : {
263 0 : case GNUNET_DB_STATUS_HARD_ERROR:
264 0 : return TALER_MHD_reply_with_error (connection,
265 : MHD_HTTP_INTERNAL_SERVER_ERROR,
266 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
267 : NULL);
268 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
269 0 : return TALER_MHD_reply_with_error (connection,
270 : MHD_HTTP_INTERNAL_SERVER_ERROR,
271 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
272 : NULL);
273 4 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
274 4 : if (NULL == contract_terms)
275 2 : return TALER_MHD_reply_with_error (connection,
276 : MHD_HTTP_NOT_FOUND,
277 : TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND,
278 : order_id);
279 : /* already claimed! */
280 2 : json_decref (contract_terms);
281 2 : return TALER_MHD_reply_with_error (connection,
282 : MHD_HTTP_CONFLICT,
283 : TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED,
284 : order_id);
285 91 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
286 91 : GNUNET_assert (NULL != contract_terms);
287 91 : break; /* Good! return signature (below) */
288 : }
289 :
290 : /* create contract signature */
291 : {
292 : struct TALER_PrivateContractHashP hash;
293 : struct TALER_MerchantSignatureP merchant_sig;
294 :
295 : /**
296 : * Hash of the JSON contract in UTF-8 including 0-termination,
297 : * using JSON_COMPACT | JSON_SORT_KEYS
298 : */
299 :
300 91 : if (GNUNET_OK !=
301 91 : TALER_JSON_contract_hash (contract_terms,
302 : &hash))
303 : {
304 0 : GNUNET_break (0);
305 0 : json_decref (contract_terms);
306 0 : return TALER_MHD_reply_with_error (connection,
307 : MHD_HTTP_INTERNAL_SERVER_ERROR,
308 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
309 : NULL);
310 : }
311 :
312 91 : TALER_merchant_contract_sign (&hash,
313 91 : &hc->instance->merchant_priv,
314 : &merchant_sig);
315 91 : return TALER_MHD_REPLY_JSON_PACK (
316 : connection,
317 : MHD_HTTP_OK,
318 : GNUNET_JSON_pack_object_steal ("contract_terms",
319 : contract_terms),
320 : GNUNET_JSON_pack_data_auto ("sig",
321 : &merchant_sig));
322 : }
323 : }
324 :
325 :
326 : /* end of taler-merchant-httpd_post-orders-ORDER_ID-claim.c */
|