Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020-2025 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-patch-instances-ID.c
22 : * @brief implementing PATCH /instances/$ID request handling
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd_private-patch-instances-ID.h"
27 : #include "taler-merchant-httpd_helper.h"
28 : #include <taler/taler_json_lib.h>
29 : #include <taler/taler_dbevents.h>
30 : #include "taler-merchant-httpd_mfa.h"
31 :
32 :
33 : /**
34 : * How often do we retry the simple INSERT database transaction?
35 : */
36 : #define MAX_RETRIES 3
37 :
38 :
39 : /**
40 : * Free memory used by @a wm
41 : *
42 : * @param wm wire method to free
43 : */
44 : static void
45 0 : free_wm (struct TMH_WireMethod *wm)
46 : {
47 0 : GNUNET_free (wm->payto_uri.full_payto);
48 0 : GNUNET_free (wm->wire_method);
49 0 : GNUNET_free (wm);
50 0 : }
51 :
52 :
53 : /**
54 : * PATCH configuration of an existing instance, given its configuration.
55 : *
56 : * @param mi instance to patch
57 : * @param connection the MHD connection to handle
58 : * @param[in,out] hc context with further information about the request
59 : * @return MHD result code
60 : */
61 : static MHD_RESULT
62 4 : patch_instances_ID (struct TMH_MerchantInstance *mi,
63 : struct MHD_Connection *connection,
64 : struct TMH_HandlerContext *hc)
65 : {
66 : struct TALER_MERCHANTDB_InstanceSettings is;
67 : const char *name;
68 4 : struct TMH_WireMethod *wm_head = NULL;
69 4 : struct TMH_WireMethod *wm_tail = NULL;
70 : bool no_transfer_delay;
71 : bool no_pay_delay;
72 : bool no_refund_delay;
73 : struct GNUNET_JSON_Specification spec[] = {
74 4 : GNUNET_JSON_spec_string ("name",
75 : &name),
76 4 : GNUNET_JSON_spec_mark_optional (
77 : GNUNET_JSON_spec_string ("website",
78 : (const char **) &is.website),
79 : NULL),
80 4 : GNUNET_JSON_spec_mark_optional (
81 : GNUNET_JSON_spec_string ("email",
82 : (const char **) &is.email),
83 : NULL),
84 4 : GNUNET_JSON_spec_mark_optional (
85 : GNUNET_JSON_spec_string ("phone_number",
86 : (const char **) &is.phone),
87 : NULL),
88 4 : GNUNET_JSON_spec_mark_optional (
89 : GNUNET_JSON_spec_string ("logo",
90 : (const char **) &is.logo),
91 : NULL),
92 4 : GNUNET_JSON_spec_json ("address",
93 : &is.address),
94 4 : GNUNET_JSON_spec_json ("jurisdiction",
95 : &is.jurisdiction),
96 4 : GNUNET_JSON_spec_bool ("use_stefan",
97 : &is.use_stefan),
98 4 : GNUNET_JSON_spec_mark_optional (
99 : GNUNET_JSON_spec_relative_time ("default_pay_delay",
100 : &is.default_pay_delay),
101 : &no_pay_delay),
102 4 : GNUNET_JSON_spec_mark_optional (
103 : GNUNET_JSON_spec_relative_time ("default_refund_delay",
104 : &is.default_refund_delay),
105 : &no_refund_delay),
106 4 : GNUNET_JSON_spec_mark_optional (
107 : GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
108 : &is.default_wire_transfer_delay),
109 : &no_transfer_delay),
110 4 : GNUNET_JSON_spec_mark_optional (
111 : GNUNET_JSON_spec_time_rounder_interval (
112 : "default_wire_transfer_rounding_interval",
113 : &is.default_wire_transfer_rounding_interval),
114 : NULL),
115 4 : GNUNET_JSON_spec_end ()
116 : };
117 : enum GNUNET_DB_QueryStatus qs;
118 :
119 4 : GNUNET_assert (NULL != mi);
120 4 : memset (&is,
121 : 0,
122 : sizeof (is));
123 : {
124 : enum GNUNET_GenericReturnValue res;
125 :
126 4 : res = TALER_MHD_parse_json_data (connection,
127 4 : hc->request_body,
128 : spec);
129 4 : if (GNUNET_OK != res)
130 : return (GNUNET_NO == res)
131 : ? MHD_YES
132 0 : : MHD_NO;
133 : }
134 4 : if (! TMH_location_object_valid (is.address))
135 : {
136 0 : GNUNET_break_op (0);
137 0 : GNUNET_JSON_parse_free (spec);
138 0 : return TALER_MHD_reply_with_error (connection,
139 : MHD_HTTP_BAD_REQUEST,
140 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
141 : "address");
142 : }
143 4 : if ( (NULL != is.logo) &&
144 0 : (! TMH_image_data_url_valid (is.logo)) )
145 : {
146 0 : GNUNET_break_op (0);
147 0 : GNUNET_JSON_parse_free (spec);
148 0 : return TALER_MHD_reply_with_error (connection,
149 : MHD_HTTP_BAD_REQUEST,
150 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
151 : "logo");
152 : }
153 :
154 4 : if (! TMH_location_object_valid (is.jurisdiction))
155 : {
156 0 : GNUNET_break_op (0);
157 0 : GNUNET_JSON_parse_free (spec);
158 0 : return TALER_MHD_reply_with_error (connection,
159 : MHD_HTTP_BAD_REQUEST,
160 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
161 : "jurisdiction");
162 : }
163 :
164 4 : if (no_transfer_delay)
165 0 : is.default_wire_transfer_delay = mi->settings.default_wire_transfer_delay;
166 4 : if (no_pay_delay)
167 0 : is.default_pay_delay = mi->settings.default_pay_delay;
168 4 : if (no_refund_delay)
169 0 : is.default_refund_delay = mi->settings.default_refund_delay;
170 4 : if (GNUNET_TIME_relative_is_forever (is.default_pay_delay))
171 : {
172 0 : GNUNET_break_op (0);
173 0 : GNUNET_JSON_parse_free (spec);
174 0 : return TALER_MHD_reply_with_error (connection,
175 : MHD_HTTP_BAD_REQUEST,
176 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
177 : "default_pay_delay");
178 : }
179 4 : if (GNUNET_TIME_relative_is_forever (is.default_refund_delay))
180 : {
181 0 : GNUNET_break_op (0);
182 0 : GNUNET_JSON_parse_free (spec);
183 0 : return TALER_MHD_reply_with_error (connection,
184 : MHD_HTTP_BAD_REQUEST,
185 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
186 : "default_refund_delay");
187 : }
188 4 : if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay))
189 : {
190 0 : GNUNET_break_op (0);
191 0 : GNUNET_JSON_parse_free (spec);
192 0 : return TALER_MHD_reply_with_error (connection,
193 : MHD_HTTP_BAD_REQUEST,
194 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
195 : "default_wire_transfer_delay");
196 : }
197 :
198 4 : if ( (NULL != is.phone) &&
199 0 : (NULL != mi->settings.phone) &&
200 0 : 0 == strcmp (mi->settings.phone,
201 0 : is.phone) )
202 0 : is.phone_validated = mi->settings.phone_validated;
203 4 : if ( (NULL != is.email) &&
204 0 : (NULL != mi->settings.email) &&
205 0 : 0 == strcmp (mi->settings.email,
206 0 : is.email) )
207 0 : is.email_validated = mi->settings.email_validated;
208 : {
209 4 : enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
210 4 : enum TEH_TanChannelSet mtc = TEH_mandatory_tan_channels;
211 :
212 4 : if ( (0 != (mtc & TEH_TCS_SMS)) &&
213 0 : (NULL == is.phone) )
214 : {
215 0 : GNUNET_break_op (0);
216 0 : GNUNET_JSON_parse_free (spec);
217 0 : return TALER_MHD_reply_with_error (
218 : connection,
219 : MHD_HTTP_BAD_REQUEST,
220 : TALER_EC_GENERIC_PARAMETER_MISSING,
221 : "phone_number");
222 : }
223 4 : if ( (0 != (mtc & TEH_TCS_EMAIL)) &&
224 0 : (NULL == is.email) )
225 : {
226 0 : GNUNET_break_op (0);
227 0 : GNUNET_JSON_parse_free (spec);
228 0 : return TALER_MHD_reply_with_error (
229 : connection,
230 : MHD_HTTP_BAD_REQUEST,
231 : TALER_EC_GENERIC_PARAMETER_MISSING,
232 : "email");
233 : }
234 4 : if ( (is.phone_validated) &&
235 0 : (0 != (mtc & TEH_TCS_SMS)) )
236 0 : mtc -= TEH_TCS_SMS;
237 4 : if ( (is.email_validated) &&
238 0 : (0 != (mtc & TEH_TCS_EMAIL)) )
239 0 : mtc -= TEH_TCS_EMAIL;
240 4 : switch (mtc)
241 : {
242 4 : case TEH_TCS_NONE:
243 4 : ret = GNUNET_OK;
244 4 : break;
245 0 : case TEH_TCS_SMS:
246 0 : if (NULL == is.phone)
247 : {
248 0 : GNUNET_break_op (0);
249 0 : GNUNET_JSON_parse_free (spec);
250 0 : return TALER_MHD_reply_with_error (
251 : connection,
252 : MHD_HTTP_BAD_REQUEST,
253 : TALER_EC_GENERIC_PARAMETER_MISSING,
254 : "phone_number");
255 : }
256 0 : is.phone_validated = true;
257 : /* validate new phone number, if possible require old e-mail
258 : address for authorization */
259 0 : ret = TMH_mfa_challenges_do (hc,
260 : TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
261 : true,
262 : TALER_MERCHANT_MFA_CHANNEL_SMS,
263 : is.phone,
264 : 0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL
265 0 : & TEH_mandatory_tan_channels)
266 : ? TALER_MERCHANT_MFA_CHANNEL_NONE
267 : : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
268 : mi->settings.email,
269 : TALER_MERCHANT_MFA_CHANNEL_NONE);
270 0 : break;
271 0 : case TEH_TCS_EMAIL:
272 0 : is.email_validated = true;
273 : /* validate new e-mail address, if possible require old phone
274 : address for authorization */
275 0 : ret = TMH_mfa_challenges_do (hc,
276 : TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
277 : true,
278 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
279 : is.email,
280 : 0 == (TALER_MERCHANT_MFA_CHANNEL_SMS
281 : & TEH_mandatory_tan_channels)
282 : ? TALER_MERCHANT_MFA_CHANNEL_NONE
283 : : TALER_MERCHANT_MFA_CHANNEL_SMS,
284 : mi->settings.phone,
285 : TALER_MERCHANT_MFA_CHANNEL_NONE);
286 0 : break;
287 0 : case TEH_TCS_EMAIL_AND_SMS:
288 0 : is.phone_validated = true;
289 0 : is.email_validated = true;
290 : /* To change both, we require both old and both new
291 : addresses to consent */
292 0 : ret = TMH_mfa_challenges_do (hc,
293 : TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
294 : true,
295 : TALER_MERCHANT_MFA_CHANNEL_SMS,
296 : mi->settings.phone,
297 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
298 : mi->settings.email,
299 : TALER_MERCHANT_MFA_CHANNEL_SMS,
300 : is.phone,
301 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
302 : is.email,
303 : TALER_MERCHANT_MFA_CHANNEL_NONE);
304 0 : break;
305 : }
306 4 : if (GNUNET_OK != ret)
307 : {
308 0 : GNUNET_JSON_parse_free (spec);
309 : return (GNUNET_NO == ret)
310 : ? MHD_YES
311 0 : : MHD_NO;
312 : }
313 :
314 : }
315 :
316 4 : for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
317 : {
318 : /* Cleanup after earlier loops */
319 : {
320 : struct TMH_WireMethod *wm;
321 :
322 4 : while (NULL != (wm = wm_head))
323 : {
324 0 : GNUNET_CONTAINER_DLL_remove (wm_head,
325 : wm_tail,
326 : wm);
327 0 : free_wm (wm);
328 : }
329 : }
330 4 : if (GNUNET_OK !=
331 4 : TMH_db->start (TMH_db->cls,
332 : "PATCH /instances"))
333 : {
334 0 : GNUNET_JSON_parse_free (spec);
335 0 : return TALER_MHD_reply_with_error (connection,
336 : MHD_HTTP_INTERNAL_SERVER_ERROR,
337 : TALER_EC_GENERIC_DB_START_FAILED,
338 : NULL);
339 : }
340 : /* Check for equality of settings */
341 4 : if (! ( (0 == strcmp (mi->settings.name,
342 2 : name)) &&
343 2 : ((mi->settings.email == is.email) ||
344 0 : (NULL != is.email && NULL != mi->settings.email &&
345 0 : 0 == strcmp (mi->settings.email,
346 0 : is.email))) &&
347 2 : ((mi->settings.phone == is.phone) ||
348 0 : (NULL != is.phone && NULL != mi->settings.phone &&
349 0 : 0 == strcmp (mi->settings.phone,
350 0 : is.phone))) &&
351 2 : ((mi->settings.website == is.website) ||
352 0 : (NULL != is.website && NULL != mi->settings.website &&
353 0 : 0 == strcmp (mi->settings.website,
354 0 : is.website))) &&
355 2 : ((mi->settings.logo == is.logo) ||
356 0 : (NULL != is.logo && NULL != mi->settings.logo &&
357 0 : 0 == strcmp (mi->settings.logo,
358 2 : is.logo))) &&
359 2 : (1 == json_equal (mi->settings.address,
360 2 : is.address)) &&
361 0 : (1 == json_equal (mi->settings.jurisdiction,
362 0 : is.jurisdiction)) &&
363 0 : (mi->settings.use_stefan == is.use_stefan) &&
364 0 : (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
365 : ==,
366 : is.default_wire_transfer_delay)) &&
367 0 : (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
368 : ==,
369 : is.default_pay_delay)) ) )
370 : {
371 4 : is.id = mi->settings.id;
372 4 : is.name = GNUNET_strdup (name);
373 4 : qs = TMH_db->update_instance (TMH_db->cls,
374 : &is);
375 4 : GNUNET_free (is.name);
376 4 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
377 : {
378 0 : TMH_db->rollback (TMH_db->cls);
379 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
380 0 : goto retry;
381 : else
382 0 : goto giveup;
383 : }
384 : }
385 4 : qs = TMH_db->commit (TMH_db->cls);
386 4 : retry:
387 4 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
388 0 : continue;
389 4 : break;
390 : } /* for(... MAX_RETRIES) */
391 0 : giveup:
392 : /* Update our 'settings' */
393 4 : GNUNET_free (mi->settings.name);
394 4 : GNUNET_free (mi->settings.email);
395 4 : GNUNET_free (mi->settings.phone);
396 4 : GNUNET_free (mi->settings.website);
397 4 : GNUNET_free (mi->settings.logo);
398 4 : json_decref (mi->settings.address);
399 4 : json_decref (mi->settings.jurisdiction);
400 4 : is.id = mi->settings.id;
401 4 : mi->settings = is;
402 4 : mi->settings.address = json_incref (mi->settings.address);
403 4 : mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
404 4 : mi->settings.name = GNUNET_strdup (name);
405 4 : if (NULL != is.email)
406 0 : mi->settings.email = GNUNET_strdup (is.email);
407 4 : if (NULL != is.phone)
408 0 : mi->settings.phone = GNUNET_strdup (is.phone);
409 4 : if (NULL != is.website)
410 0 : mi->settings.website = GNUNET_strdup (is.website);
411 4 : if (NULL != is.logo)
412 0 : mi->settings.logo = GNUNET_strdup (is.logo);
413 :
414 4 : GNUNET_JSON_parse_free (spec);
415 4 : TMH_reload_instances (mi->settings.id);
416 4 : return TALER_MHD_reply_static (connection,
417 : MHD_HTTP_NO_CONTENT,
418 : NULL,
419 : NULL,
420 : 0);
421 : }
422 :
423 :
424 : MHD_RESULT
425 0 : TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
426 : struct MHD_Connection *connection,
427 : struct TMH_HandlerContext *hc)
428 : {
429 0 : struct TMH_MerchantInstance *mi = hc->instance;
430 :
431 0 : return patch_instances_ID (mi,
432 : connection,
433 : hc);
434 : }
435 :
436 :
437 : MHD_RESULT
438 4 : TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh,
439 : struct MHD_Connection *connection,
440 : struct TMH_HandlerContext *hc)
441 : {
442 : struct TMH_MerchantInstance *mi;
443 :
444 4 : mi = TMH_lookup_instance (hc->infix);
445 4 : if (NULL == mi)
446 : {
447 0 : return TALER_MHD_reply_with_error (connection,
448 : MHD_HTTP_NOT_FOUND,
449 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
450 0 : hc->infix);
451 : }
452 4 : if (mi->deleted)
453 : {
454 0 : return TALER_MHD_reply_with_error (connection,
455 : MHD_HTTP_CONFLICT,
456 : TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED,
457 0 : hc->infix);
458 : }
459 4 : return patch_instances_ID (mi,
460 : connection,
461 : hc);
462 : }
463 :
464 :
465 : /* end of taler-merchant-httpd_private-patch-instances-ID.c */
|