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