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