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 64 : 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 64 : if (GNUNET_OK !=
67 64 : TMH_db->start (TMH_db->cls,
68 : "claim order"))
69 : {
70 0 : GNUNET_break (0);
71 0 : return GNUNET_DB_STATUS_HARD_ERROR;
72 : }
73 64 : qs = TMH_db->lookup_contract_terms (TMH_db->cls,
74 : instance_id,
75 : order_id,
76 : contract_terms,
77 : &order_serial,
78 : NULL);
79 64 : if (0 > qs)
80 : {
81 0 : TMH_db->rollback (TMH_db->cls);
82 0 : return qs;
83 : }
84 :
85 64 : 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 10 : GNUNET_JSON_spec_string ("nonce",
91 : &stored_nonce),
92 10 : GNUNET_JSON_spec_end ()
93 : };
94 :
95 10 : TMH_db->rollback (TMH_db->cls);
96 10 : GNUNET_assert (NULL != *contract_terms);
97 :
98 10 : if (GNUNET_OK !=
99 10 : 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 0 : GNUNET_break (0);
107 0 : return GNUNET_DB_STATUS_HARD_ERROR;
108 : }
109 :
110 10 : if (0 != strcmp (stored_nonce,
111 : nonce))
112 : {
113 2 : GNUNET_JSON_parse_free (spec);
114 2 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
115 : }
116 8 : GNUNET_JSON_parse_free (spec);
117 8 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
118 : }
119 :
120 54 : 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 54 : GNUNET_JSON_spec_timestamp ("timestamp",
128 : ×tamp),
129 54 : GNUNET_JSON_spec_end ()
130 : };
131 :
132 : /* see if we have this order in our table of unclaimed orders */
133 54 : qs = TMH_db->lookup_order (TMH_db->cls,
134 : instance_id,
135 : order_id,
136 : &order_ct,
137 : &unused,
138 : contract_terms);
139 54 : if (0 >= qs)
140 : {
141 2 : TMH_db->rollback (TMH_db->cls);
142 2 : return qs;
143 : }
144 52 : GNUNET_assert (NULL != *contract_terms);
145 52 : if (GNUNET_OK !=
146 52 : 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 0 : GNUNET_break (0);
154 0 : TMH_db->rollback (TMH_db->cls);
155 0 : return GNUNET_DB_STATUS_HARD_ERROR;
156 : }
157 :
158 52 : GNUNET_assert (0 ==
159 : json_object_set_new (*contract_terms,
160 : "nonce",
161 : json_string (nonce)));
162 52 : if (0 != GNUNET_memcmp_priv (&order_ct,
163 : claim_token))
164 : {
165 0 : TMH_db->rollback (TMH_db->cls);
166 0 : json_decref (*contract_terms);
167 0 : *contract_terms = NULL;
168 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
169 : }
170 52 : qs = TMH_db->insert_contract_terms (TMH_db->cls,
171 : instance_id,
172 : order_id,
173 : *contract_terms,
174 : &order_serial);
175 52 : if (0 >= qs)
176 : {
177 0 : TMH_db->rollback (TMH_db->cls);
178 0 : json_decref (*contract_terms);
179 0 : *contract_terms = NULL;
180 0 : return qs;
181 : }
182 52 : TMH_notify_order_change (TMH_lookup_instance (instance_id),
183 : TMH_OSF_CLAIMED,
184 : timestamp,
185 : order_serial);
186 52 : qs = TMH_db->commit (TMH_db->cls);
187 52 : if (0 > qs)
188 0 : return qs;
189 52 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
190 : }
191 : }
192 :
193 :
194 : MHD_RESULT
195 64 : TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
196 : struct MHD_Connection *connection,
197 : struct TMH_HandlerContext *hc)
198 : {
199 64 : const char *order_id = hc->infix;
200 : const char *nonce;
201 : enum GNUNET_DB_QueryStatus qs;
202 : json_t *contract_terms;
203 64 : struct TALER_ClaimTokenP claim_token = { 0 };
204 :
205 : {
206 : struct GNUNET_JSON_Specification spec[] = {
207 64 : GNUNET_JSON_spec_string ("nonce",
208 : &nonce),
209 64 : GNUNET_JSON_spec_mark_optional (
210 : GNUNET_JSON_spec_fixed_auto ("token",
211 : &claim_token),
212 : NULL),
213 64 : GNUNET_JSON_spec_end ()
214 : };
215 : enum GNUNET_GenericReturnValue res;
216 :
217 64 : res = TALER_MHD_parse_json_data (connection,
218 64 : hc->request_body,
219 : spec);
220 64 : if (GNUNET_OK != res)
221 : {
222 0 : GNUNET_break_op (0);
223 0 : json_dumpf (hc->request_body,
224 : stderr,
225 : JSON_INDENT (2));
226 : return (GNUNET_NO == res)
227 : ? MHD_YES
228 0 : : MHD_NO;
229 : }
230 : }
231 64 : contract_terms = NULL;
232 64 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
233 : {
234 64 : TMH_db->preflight (TMH_db->cls);
235 64 : qs = claim_order (hc->instance->settings.id,
236 : order_id,
237 : nonce,
238 : &claim_token,
239 : &contract_terms);
240 64 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
241 64 : break;
242 : }
243 64 : switch (qs)
244 : {
245 0 : case GNUNET_DB_STATUS_HARD_ERROR:
246 0 : return TALER_MHD_reply_with_error (connection,
247 : MHD_HTTP_INTERNAL_SERVER_ERROR,
248 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
249 : NULL);
250 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
251 0 : return TALER_MHD_reply_with_error (connection,
252 : MHD_HTTP_INTERNAL_SERVER_ERROR,
253 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
254 : NULL);
255 4 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
256 4 : if (NULL == contract_terms)
257 2 : 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 2 : json_decref (contract_terms);
263 2 : 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 60 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
268 60 : GNUNET_assert (NULL != contract_terms);
269 60 : 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 60 : if (GNUNET_OK !=
283 60 : TALER_JSON_contract_hash (contract_terms,
284 : &hash))
285 : {
286 0 : GNUNET_break (0);
287 0 : json_decref (contract_terms);
288 0 : 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 60 : TALER_merchant_contract_sign (&hash,
295 60 : &hc->instance->merchant_priv,
296 : &merchant_sig);
297 60 : 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 */
|