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 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 0 : 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 0 : bool paid = false;
66 :
67 0 : if (GNUNET_OK !=
68 0 : TMH_db->start (TMH_db->cls,
69 : "claim order"))
70 : {
71 0 : GNUNET_break (0);
72 0 : return GNUNET_DB_STATUS_HARD_ERROR;
73 : }
74 0 : qs = TMH_db->lookup_contract_terms (TMH_db->cls,
75 : instance_id,
76 : order_id,
77 : contract_terms,
78 : &order_serial,
79 : &paid,
80 : NULL);
81 0 : if (0 > qs)
82 : {
83 0 : TMH_db->rollback (TMH_db->cls);
84 0 : return qs;
85 : }
86 :
87 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
88 : {
89 : /* We already have claimed contract terms for this order_id */
90 : const char *stored_nonce;
91 : struct GNUNET_JSON_Specification spec[] = {
92 0 : GNUNET_JSON_spec_string ("nonce",
93 : &stored_nonce),
94 0 : GNUNET_JSON_spec_end ()
95 : };
96 :
97 0 : TMH_db->rollback (TMH_db->cls);
98 0 : GNUNET_assert (NULL != *contract_terms);
99 :
100 0 : if (GNUNET_OK !=
101 0 : GNUNET_JSON_parse (*contract_terms,
102 : spec,
103 : NULL,
104 : NULL))
105 : {
106 : /* this should not be possible: contract_terms should always
107 : have a nonce! */
108 0 : GNUNET_break (0);
109 0 : return GNUNET_DB_STATUS_HARD_ERROR;
110 : }
111 :
112 0 : if (0 != strcmp (stored_nonce,
113 : nonce))
114 : {
115 0 : GNUNET_JSON_parse_free (spec);
116 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
117 : }
118 0 : GNUNET_JSON_parse_free (spec);
119 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
120 : }
121 :
122 0 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
123 :
124 : /* Now we need to claim the order. */
125 : {
126 : struct TALER_MerchantPostDataHashP unused;
127 : struct GNUNET_TIME_Timestamp timestamp;
128 : struct GNUNET_JSON_Specification spec[] = {
129 0 : GNUNET_JSON_spec_timestamp ("timestamp",
130 : ×tamp),
131 0 : GNUNET_JSON_spec_end ()
132 : };
133 :
134 : /* see if we have this order in our table of unclaimed orders */
135 0 : qs = TMH_db->lookup_order (TMH_db->cls,
136 : instance_id,
137 : order_id,
138 : &order_ct,
139 : &unused,
140 : contract_terms);
141 0 : if (0 >= qs)
142 : {
143 0 : TMH_db->rollback (TMH_db->cls);
144 0 : return qs;
145 : }
146 0 : GNUNET_assert (NULL != *contract_terms);
147 0 : if (GNUNET_OK !=
148 0 : GNUNET_JSON_parse (*contract_terms,
149 : spec,
150 : NULL,
151 : NULL))
152 : {
153 : /* this should not be possible: contract_terms should always
154 : have a timestamp! */
155 0 : GNUNET_break (0);
156 0 : TMH_db->rollback (TMH_db->cls);
157 0 : return GNUNET_DB_STATUS_HARD_ERROR;
158 : }
159 :
160 0 : GNUNET_assert (0 ==
161 : json_object_set_new (*contract_terms,
162 : "nonce",
163 : json_string (nonce)));
164 0 : if (0 != GNUNET_memcmp_priv (&order_ct,
165 : claim_token))
166 : {
167 0 : TMH_db->rollback (TMH_db->cls);
168 0 : json_decref (*contract_terms);
169 0 : *contract_terms = NULL;
170 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
171 : }
172 0 : qs = TMH_db->insert_contract_terms (TMH_db->cls,
173 : instance_id,
174 : order_id,
175 : *contract_terms,
176 : &order_serial);
177 0 : if (0 >= qs)
178 : {
179 0 : TMH_db->rollback (TMH_db->cls);
180 0 : json_decref (*contract_terms);
181 0 : *contract_terms = NULL;
182 0 : return qs;
183 : }
184 0 : TMH_notify_order_change (TMH_lookup_instance (instance_id),
185 : TMH_OSF_CLAIMED,
186 : timestamp,
187 : order_serial);
188 0 : qs = TMH_db->commit (TMH_db->cls);
189 0 : if (0 > qs)
190 0 : return qs;
191 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
192 : }
193 : }
194 :
195 :
196 : MHD_RESULT
197 0 : TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
198 : struct MHD_Connection *connection,
199 : struct TMH_HandlerContext *hc)
200 : {
201 0 : const char *order_id = hc->infix;
202 : const char *nonce;
203 : enum GNUNET_DB_QueryStatus qs;
204 : json_t *contract_terms;
205 0 : struct TALER_ClaimTokenP claim_token = { 0 };
206 :
207 : {
208 : struct GNUNET_JSON_Specification spec[] = {
209 0 : GNUNET_JSON_spec_string ("nonce",
210 : &nonce),
211 0 : GNUNET_JSON_spec_mark_optional (
212 : GNUNET_JSON_spec_fixed_auto ("token",
213 : &claim_token),
214 : NULL),
215 0 : GNUNET_JSON_spec_end ()
216 : };
217 : enum GNUNET_GenericReturnValue res;
218 :
219 0 : res = TALER_MHD_parse_json_data (connection,
220 0 : hc->request_body,
221 : spec);
222 0 : if (GNUNET_OK != res)
223 : {
224 0 : GNUNET_break_op (0);
225 0 : json_dumpf (hc->request_body,
226 : stderr,
227 : JSON_INDENT (2));
228 : return (GNUNET_NO == res)
229 : ? MHD_YES
230 0 : : MHD_NO;
231 : }
232 : }
233 0 : contract_terms = NULL;
234 0 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
235 : {
236 0 : TMH_db->preflight (TMH_db->cls);
237 0 : qs = claim_order (hc->instance->settings.id,
238 : order_id,
239 : nonce,
240 : &claim_token,
241 : &contract_terms);
242 0 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
243 0 : break;
244 : }
245 0 : switch (qs)
246 : {
247 0 : case GNUNET_DB_STATUS_HARD_ERROR:
248 0 : return TALER_MHD_reply_with_error (connection,
249 : MHD_HTTP_INTERNAL_SERVER_ERROR,
250 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
251 : NULL);
252 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
253 0 : return TALER_MHD_reply_with_error (connection,
254 : MHD_HTTP_INTERNAL_SERVER_ERROR,
255 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
256 : NULL);
257 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
258 0 : if (NULL == contract_terms)
259 0 : return TALER_MHD_reply_with_error (connection,
260 : MHD_HTTP_NOT_FOUND,
261 : TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND,
262 : order_id);
263 : /* already claimed! */
264 0 : json_decref (contract_terms);
265 0 : return TALER_MHD_reply_with_error (connection,
266 : MHD_HTTP_CONFLICT,
267 : TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED,
268 : order_id);
269 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
270 0 : GNUNET_assert (NULL != contract_terms);
271 0 : break; /* Good! return signature (below) */
272 : }
273 :
274 : /* create contract signature */
275 0 : {
276 : struct TALER_PrivateContractHashP hash;
277 : struct GNUNET_CRYPTO_EddsaSignature merchant_sig;
278 :
279 : /**
280 : * Hash of the JSON contract in UTF-8 including 0-termination,
281 : * using JSON_COMPACT | JSON_SORT_KEYS
282 : */
283 :
284 0 : if (GNUNET_OK !=
285 0 : TALER_JSON_contract_hash (contract_terms,
286 : &hash))
287 : {
288 0 : GNUNET_break (0);
289 0 : json_decref (contract_terms);
290 0 : return TALER_MHD_reply_with_error (connection,
291 : MHD_HTTP_INTERNAL_SERVER_ERROR,
292 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
293 : NULL);
294 : }
295 :
296 0 : TALER_merchant_contract_sign (&hash,
297 0 : &hc->instance->merchant_priv,
298 : &merchant_sig);
299 0 : return TALER_MHD_REPLY_JSON_PACK (
300 : connection,
301 : MHD_HTTP_OK,
302 : GNUNET_JSON_pack_object_steal ("contract_terms",
303 : contract_terms),
304 : GNUNET_JSON_pack_data_auto ("sig",
305 : &merchant_sig));
306 : }
307 : }
308 :
309 :
310 : /* end of taler-merchant-httpd_post-orders-ID-claim.c */
|