Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020-2023 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_private-post-instances.c
22 : * @brief implementing POST /instances request handling
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd_private-post-instances.h"
27 : #include "taler-merchant-httpd_helper.h"
28 : #include "taler_merchant_bank_lib.h"
29 : #include <taler/taler_dbevents.h>
30 : #include <taler/taler_json_lib.h>
31 : #include <regex.h>
32 :
33 : /**
34 : * How often do we retry the simple INSERT database transaction?
35 : */
36 : #define MAX_RETRIES 3
37 :
38 :
39 : /**
40 : * Generate an instance, given its configuration.
41 : *
42 : * @param rh context of the handler
43 : * @param connection the MHD connection to handle
44 : * @param[in,out] hc context with further information about the request
45 : * @return MHD result code
46 : */
47 : MHD_RESULT
48 31 : TMH_private_post_instances (const struct TMH_RequestHandler *rh,
49 : struct MHD_Connection *connection,
50 : struct TMH_HandlerContext *hc)
51 : {
52 31 : struct TALER_MERCHANTDB_InstanceSettings is = { 0 };
53 : struct TALER_MERCHANTDB_InstanceAuthSettings ias;
54 31 : const char *auth_password = NULL;
55 31 : const char *uts = "business";
56 31 : struct TMH_WireMethod *wm_head = NULL;
57 31 : struct TMH_WireMethod *wm_tail = NULL;
58 : const json_t *jauth;
59 : struct GNUNET_JSON_Specification spec[] = {
60 31 : GNUNET_JSON_spec_string ("id",
61 : (const char **) &is.id),
62 31 : GNUNET_JSON_spec_string ("name",
63 : (const char **) &is.name),
64 31 : GNUNET_JSON_spec_mark_optional (
65 : GNUNET_JSON_spec_string ("user_type",
66 : &uts),
67 : NULL),
68 31 : GNUNET_JSON_spec_mark_optional (
69 : GNUNET_JSON_spec_string ("email",
70 : (const char **) &is.email),
71 : NULL),
72 31 : GNUNET_JSON_spec_mark_optional (
73 : GNUNET_JSON_spec_string ("website",
74 : (const char **) &is.website),
75 : NULL),
76 31 : GNUNET_JSON_spec_mark_optional (
77 : GNUNET_JSON_spec_string ("logo",
78 : (const char **) &is.logo),
79 : NULL),
80 31 : GNUNET_JSON_spec_object_const ("auth",
81 : &jauth),
82 31 : GNUNET_JSON_spec_json ("address",
83 : &is.address),
84 31 : GNUNET_JSON_spec_json ("jurisdiction",
85 : &is.jurisdiction),
86 31 : GNUNET_JSON_spec_bool ("use_stefan",
87 : &is.use_stefan),
88 31 : GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
89 : &is.default_wire_transfer_delay),
90 31 : GNUNET_JSON_spec_relative_time ("default_pay_delay",
91 : &is.default_pay_delay),
92 31 : GNUNET_JSON_spec_end ()
93 : };
94 :
95 : {
96 : enum GNUNET_GenericReturnValue res;
97 :
98 31 : res = TALER_MHD_parse_json_data (connection,
99 31 : hc->request_body,
100 : spec);
101 31 : if (GNUNET_OK != res)
102 : return (GNUNET_NO == res)
103 : ? MHD_YES
104 0 : : MHD_NO;
105 : }
106 : {
107 : enum GNUNET_GenericReturnValue ret;
108 :
109 31 : ret = TMH_check_auth_config (connection,
110 : jauth,
111 : &auth_password);
112 31 : if (GNUNET_OK != ret)
113 : {
114 0 : GNUNET_JSON_parse_free (spec);
115 0 : return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
116 : }
117 : }
118 :
119 : /* check 'id' well-formed */
120 : {
121 : static bool once;
122 : static regex_t reg;
123 31 : bool id_wellformed = true;
124 :
125 31 : if (! once)
126 : {
127 14 : once = true;
128 14 : GNUNET_assert (0 ==
129 : regcomp (®,
130 : "^[A-Za-z0-9][A-Za-z0-9_.@-]+$",
131 : REG_EXTENDED));
132 : }
133 :
134 31 : if (0 != regexec (®,
135 31 : is.id,
136 : 0, NULL, 0))
137 0 : id_wellformed = false;
138 31 : if (! id_wellformed)
139 : {
140 0 : GNUNET_JSON_parse_free (spec);
141 0 : return TALER_MHD_reply_with_error (connection,
142 : MHD_HTTP_BAD_REQUEST,
143 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
144 : "id");
145 : }
146 : }
147 :
148 31 : if (! TMH_location_object_valid (is.address))
149 : {
150 0 : GNUNET_break_op (0);
151 0 : GNUNET_JSON_parse_free (spec);
152 0 : return TALER_MHD_reply_with_error (connection,
153 : MHD_HTTP_BAD_REQUEST,
154 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
155 : "address");
156 : }
157 :
158 31 : if (! TMH_location_object_valid (is.jurisdiction))
159 : {
160 0 : GNUNET_break_op (0);
161 0 : GNUNET_JSON_parse_free (spec);
162 0 : return TALER_MHD_reply_with_error (connection,
163 : MHD_HTTP_BAD_REQUEST,
164 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
165 : "jurisdiction");
166 : }
167 :
168 31 : if ( (NULL != is.logo) &&
169 0 : (! TMH_image_data_url_valid (is.logo)) )
170 : {
171 0 : GNUNET_break_op (0);
172 0 : GNUNET_JSON_parse_free (spec);
173 0 : return TALER_MHD_reply_with_error (connection,
174 : MHD_HTTP_BAD_REQUEST,
175 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
176 : "logo");
177 : }
178 :
179 : {
180 : /* Test if an instance of this id is known */
181 : struct TMH_MerchantInstance *mi;
182 :
183 31 : mi = TMH_lookup_instance (is.id);
184 31 : if (NULL != mi)
185 : {
186 2 : if (mi->deleted)
187 : {
188 0 : GNUNET_JSON_parse_free (spec);
189 0 : return TALER_MHD_reply_with_error (connection,
190 : MHD_HTTP_CONFLICT,
191 : TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
192 0 : is.id);
193 : }
194 : /* Check for idempotency */
195 2 : if ( (0 == strcmp (mi->settings.id,
196 2 : is.id)) &&
197 2 : (0 == strcmp (mi->settings.name,
198 2 : is.name)) &&
199 2 : ((mi->settings.email == is.email) ||
200 0 : (NULL != is.email && NULL != mi->settings.email &&
201 0 : 0 == strcmp (mi->settings.email,
202 0 : is.email))) &&
203 2 : ((mi->settings.website == is.website) ||
204 0 : (NULL != is.website && NULL != mi->settings.website &&
205 0 : 0 == strcmp (mi->settings.website,
206 0 : is.website))) &&
207 2 : ((mi->settings.logo == is.logo) ||
208 0 : (NULL != is.logo && NULL != mi->settings.logo &&
209 0 : 0 == strcmp (mi->settings.logo,
210 0 : is.logo))) &&
211 2 : ( ( (NULL != auth_password) &&
212 : (GNUNET_OK ==
213 0 : TMH_check_auth (auth_password,
214 : &mi->auth.auth_salt,
215 2 : &mi->auth.auth_hash)) ) ||
216 4 : ( (NULL == auth_password) &&
217 : (GNUNET_YES ==
218 4 : GNUNET_is_zero (&mi->auth.auth_hash))) ) &&
219 2 : (1 == json_equal (mi->settings.address,
220 4 : is.address)) &&
221 2 : (1 == json_equal (mi->settings.jurisdiction,
222 2 : is.jurisdiction)) &&
223 2 : (mi->settings.use_stefan == is.use_stefan) &&
224 2 : (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
225 : ==,
226 2 : is.default_wire_transfer_delay)) &&
227 2 : (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
228 : ==,
229 : is.default_pay_delay)) )
230 : {
231 2 : GNUNET_JSON_parse_free (spec);
232 2 : return TALER_MHD_reply_static (connection,
233 : MHD_HTTP_NO_CONTENT,
234 : NULL,
235 : NULL,
236 : 0);
237 : }
238 0 : GNUNET_JSON_parse_free (spec);
239 0 : return TALER_MHD_reply_with_error (connection,
240 : MHD_HTTP_CONFLICT,
241 : TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
242 0 : is.id);
243 : }
244 : }
245 :
246 : /* handle authentication token setup */
247 29 : if (NULL == auth_password)
248 : {
249 23 : memset (&ias.auth_salt,
250 : 0,
251 : sizeof (ias.auth_salt));
252 23 : memset (&ias.auth_hash,
253 : 0,
254 : sizeof (ias.auth_hash));
255 : }
256 : else
257 : {
258 : /* Sets 'auth_salt' and 'auth_hash' */
259 6 : TMH_compute_auth (auth_password,
260 : &ias.auth_salt,
261 : &ias.auth_hash);
262 : }
263 :
264 : /* create in-memory data structure */
265 : {
266 : struct TMH_MerchantInstance *mi;
267 : enum GNUNET_DB_QueryStatus qs;
268 :
269 29 : mi = GNUNET_new (struct TMH_MerchantInstance);
270 29 : mi->wm_head = wm_head;
271 29 : mi->wm_tail = wm_tail;
272 29 : mi->settings = is;
273 29 : mi->settings.address = json_incref (mi->settings.address);
274 29 : mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
275 29 : mi->settings.id = GNUNET_strdup (is.id);
276 29 : mi->settings.name = GNUNET_strdup (is.name);
277 29 : if (NULL != is.email)
278 0 : mi->settings.email = GNUNET_strdup (is.email);
279 29 : if (NULL != is.website)
280 0 : mi->settings.website = GNUNET_strdup (is.website);
281 29 : if (NULL != is.logo)
282 0 : mi->settings.logo = GNUNET_strdup (is.logo);
283 29 : mi->auth = ias;
284 29 : GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv);
285 29 : GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv,
286 : &mi->merchant_pub.eddsa_pub);
287 :
288 29 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
289 : {
290 29 : if (GNUNET_OK !=
291 29 : TMH_db->start (TMH_db->cls,
292 : "post /instances"))
293 : {
294 0 : mi->rc = 1;
295 0 : TMH_instance_decref (mi);
296 0 : GNUNET_JSON_parse_free (spec);
297 0 : return TALER_MHD_reply_with_error (connection,
298 : MHD_HTTP_INTERNAL_SERVER_ERROR,
299 : TALER_EC_GENERIC_DB_START_FAILED,
300 : NULL);
301 : }
302 29 : qs = TMH_db->insert_instance (TMH_db->cls,
303 29 : &mi->merchant_pub,
304 29 : &mi->merchant_priv,
305 29 : &mi->settings,
306 29 : &mi->auth);
307 29 : switch (qs)
308 : {
309 0 : case GNUNET_DB_STATUS_HARD_ERROR:
310 : {
311 : MHD_RESULT ret;
312 :
313 0 : TMH_db->rollback (TMH_db->cls);
314 0 : GNUNET_break (0);
315 0 : ret = TALER_MHD_reply_with_error (connection,
316 : MHD_HTTP_INTERNAL_SERVER_ERROR,
317 : TALER_EC_GENERIC_DB_STORE_FAILED,
318 0 : is.id);
319 0 : mi->rc = 1;
320 0 : TMH_instance_decref (mi);
321 0 : GNUNET_JSON_parse_free (spec);
322 0 : return ret;
323 : }
324 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
325 0 : goto retry;
326 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
327 : {
328 : MHD_RESULT ret;
329 :
330 0 : TMH_db->rollback (TMH_db->cls);
331 0 : GNUNET_break (0);
332 0 : ret = TALER_MHD_reply_with_error (connection,
333 : MHD_HTTP_CONFLICT,
334 : TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
335 0 : is.id);
336 0 : mi->rc = 1;
337 0 : TMH_instance_decref (mi);
338 0 : GNUNET_JSON_parse_free (spec);
339 0 : return ret;
340 : }
341 29 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
342 : /* handled below */
343 29 : break;
344 : }
345 29 : qs = TMH_db->commit (TMH_db->cls);
346 29 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
347 29 : qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
348 0 : retry:
349 29 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
350 29 : break; /* success! -- or hard failure */
351 : } /* for .. MAX_RETRIES */
352 29 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
353 : {
354 0 : mi->rc = 1;
355 0 : TMH_instance_decref (mi);
356 0 : GNUNET_JSON_parse_free (spec);
357 0 : return TALER_MHD_reply_with_error (connection,
358 : MHD_HTTP_INTERNAL_SERVER_ERROR,
359 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
360 : NULL);
361 : }
362 : /* Finally, also update our running process */
363 29 : GNUNET_assert (GNUNET_OK ==
364 : TMH_add_instance (mi));
365 29 : TMH_reload_instances (mi->settings.id);
366 : }
367 29 : GNUNET_JSON_parse_free (spec);
368 29 : return TALER_MHD_reply_static (connection,
369 : MHD_HTTP_NO_CONTENT,
370 : NULL,
371 : NULL,
372 : 0);
373 : }
374 :
375 :
376 : /* end of taler-merchant-httpd_private-post-instances.c */
|