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 :
171 4 : if ( (NULL != is.phone) &&
172 0 : (NULL != mi->settings.phone) &&
173 0 : 0 == strcmp (mi->settings.phone,
174 0 : is.phone) )
175 0 : is.phone_validated = mi->settings.phone_validated;
176 4 : if ( (NULL != is.email) &&
177 0 : (NULL != mi->settings.email) &&
178 0 : 0 == strcmp (mi->settings.email,
179 0 : is.email) )
180 0 : is.email_validated = mi->settings.email_validated;
181 : {
182 4 : enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
183 4 : enum TEH_TanChannelSet mtc = TEH_mandatory_tan_channels;
184 :
185 4 : if ( (0 != (mtc & TEH_TCS_SMS)) &&
186 0 : (NULL == is.phone) )
187 : {
188 0 : GNUNET_break_op (0);
189 0 : GNUNET_JSON_parse_free (spec);
190 0 : return TALER_MHD_reply_with_error (
191 : connection,
192 : MHD_HTTP_BAD_REQUEST,
193 : TALER_EC_GENERIC_PARAMETER_MISSING,
194 : "phone_number");
195 : }
196 4 : if ( (0 != (mtc & TEH_TCS_EMAIL)) &&
197 0 : (NULL == is.email) )
198 : {
199 0 : GNUNET_break_op (0);
200 0 : GNUNET_JSON_parse_free (spec);
201 0 : return TALER_MHD_reply_with_error (
202 : connection,
203 : MHD_HTTP_BAD_REQUEST,
204 : TALER_EC_GENERIC_PARAMETER_MISSING,
205 : "email");
206 : }
207 4 : if ( (is.phone_validated) &&
208 0 : (0 != (mtc & TEH_TCS_SMS)) )
209 0 : mtc -= TEH_TCS_SMS;
210 4 : if ( (is.email_validated) &&
211 0 : (0 != (mtc & TEH_TCS_EMAIL)) )
212 0 : mtc -= TEH_TCS_EMAIL;
213 4 : switch (mtc)
214 : {
215 4 : case TEH_TCS_NONE:
216 4 : ret = GNUNET_OK;
217 4 : break;
218 0 : case TEH_TCS_SMS:
219 0 : if (NULL == is.phone)
220 : {
221 0 : GNUNET_break_op (0);
222 0 : GNUNET_JSON_parse_free (spec);
223 0 : return TALER_MHD_reply_with_error (
224 : connection,
225 : MHD_HTTP_BAD_REQUEST,
226 : TALER_EC_GENERIC_PARAMETER_MISSING,
227 : "phone_number");
228 : }
229 0 : is.phone_validated = true;
230 : /* validate new phone number, if possible require old e-mail
231 : address for authorization */
232 0 : ret = TMH_mfa_challenges_do (hc,
233 : TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
234 : true,
235 : TALER_MERCHANT_MFA_CHANNEL_SMS,
236 : is.phone,
237 : 0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL
238 0 : & TEH_mandatory_tan_channels)
239 : ? TALER_MERCHANT_MFA_CHANNEL_NONE
240 : : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
241 : mi->settings.email,
242 : TALER_MERCHANT_MFA_CHANNEL_NONE);
243 0 : break;
244 0 : case TEH_TCS_EMAIL:
245 0 : is.email_validated = true;
246 : /* validate new e-mail address, if possible require old phone
247 : address for authorization */
248 0 : ret = TMH_mfa_challenges_do (hc,
249 : TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
250 : true,
251 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
252 : is.email,
253 : 0 == (TALER_MERCHANT_MFA_CHANNEL_SMS
254 : & TEH_mandatory_tan_channels)
255 : ? TALER_MERCHANT_MFA_CHANNEL_NONE
256 : : TALER_MERCHANT_MFA_CHANNEL_SMS,
257 : mi->settings.phone,
258 : TALER_MERCHANT_MFA_CHANNEL_NONE);
259 0 : break;
260 0 : case TEH_TCS_EMAIL_AND_SMS:
261 0 : is.phone_validated = true;
262 0 : is.email_validated = true;
263 : /* To change both, we require both old and both new
264 : addresses to consent */
265 0 : ret = TMH_mfa_challenges_do (hc,
266 : TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
267 : true,
268 : TALER_MERCHANT_MFA_CHANNEL_SMS,
269 : mi->settings.phone,
270 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
271 : mi->settings.email,
272 : TALER_MERCHANT_MFA_CHANNEL_SMS,
273 : is.phone,
274 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
275 : is.email,
276 : TALER_MERCHANT_MFA_CHANNEL_NONE);
277 0 : break;
278 : }
279 4 : if (GNUNET_OK != ret)
280 : {
281 0 : GNUNET_JSON_parse_free (spec);
282 : return (GNUNET_NO == ret)
283 : ? MHD_YES
284 0 : : MHD_NO;
285 : }
286 :
287 : }
288 :
289 4 : for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
290 : {
291 : /* Cleanup after earlier loops */
292 : {
293 : struct TMH_WireMethod *wm;
294 :
295 4 : while (NULL != (wm = wm_head))
296 : {
297 0 : GNUNET_CONTAINER_DLL_remove (wm_head,
298 : wm_tail,
299 : wm);
300 0 : free_wm (wm);
301 : }
302 : }
303 4 : if (GNUNET_OK !=
304 4 : TMH_db->start (TMH_db->cls,
305 : "PATCH /instances"))
306 : {
307 0 : GNUNET_JSON_parse_free (spec);
308 0 : return TALER_MHD_reply_with_error (connection,
309 : MHD_HTTP_INTERNAL_SERVER_ERROR,
310 : TALER_EC_GENERIC_DB_START_FAILED,
311 : NULL);
312 : }
313 : /* Check for equality of settings */
314 4 : if (! ( (0 == strcmp (mi->settings.name,
315 2 : name)) &&
316 2 : ((mi->settings.email == is.email) ||
317 0 : (NULL != is.email && NULL != mi->settings.email &&
318 0 : 0 == strcmp (mi->settings.email,
319 0 : is.email))) &&
320 2 : ((mi->settings.phone == is.phone) ||
321 0 : (NULL != is.phone && NULL != mi->settings.phone &&
322 0 : 0 == strcmp (mi->settings.phone,
323 0 : is.phone))) &&
324 2 : ((mi->settings.website == is.website) ||
325 0 : (NULL != is.website && NULL != mi->settings.website &&
326 0 : 0 == strcmp (mi->settings.website,
327 0 : is.website))) &&
328 2 : ((mi->settings.logo == is.logo) ||
329 0 : (NULL != is.logo && NULL != mi->settings.logo &&
330 0 : 0 == strcmp (mi->settings.logo,
331 2 : is.logo))) &&
332 2 : (1 == json_equal (mi->settings.address,
333 2 : is.address)) &&
334 0 : (1 == json_equal (mi->settings.jurisdiction,
335 0 : is.jurisdiction)) &&
336 0 : (mi->settings.use_stefan == is.use_stefan) &&
337 0 : (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
338 : ==,
339 : is.default_wire_transfer_delay)) &&
340 0 : (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
341 : ==,
342 : is.default_pay_delay)) ) )
343 : {
344 4 : is.id = mi->settings.id;
345 4 : is.name = GNUNET_strdup (name);
346 4 : qs = TMH_db->update_instance (TMH_db->cls,
347 : &is);
348 4 : GNUNET_free (is.name);
349 4 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
350 : {
351 0 : TMH_db->rollback (TMH_db->cls);
352 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
353 0 : goto retry;
354 : else
355 0 : goto giveup;
356 : }
357 : }
358 4 : qs = TMH_db->commit (TMH_db->cls);
359 4 : retry:
360 4 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
361 0 : continue;
362 4 : break;
363 : } /* for(... MAX_RETRIES) */
364 0 : giveup:
365 : /* Update our 'settings' */
366 4 : GNUNET_free (mi->settings.name);
367 4 : GNUNET_free (mi->settings.email);
368 4 : GNUNET_free (mi->settings.phone);
369 4 : GNUNET_free (mi->settings.website);
370 4 : GNUNET_free (mi->settings.logo);
371 4 : json_decref (mi->settings.address);
372 4 : json_decref (mi->settings.jurisdiction);
373 4 : is.id = mi->settings.id;
374 4 : mi->settings = is;
375 4 : mi->settings.address = json_incref (mi->settings.address);
376 4 : mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
377 4 : mi->settings.name = GNUNET_strdup (name);
378 4 : if (NULL != is.email)
379 0 : mi->settings.email = GNUNET_strdup (is.email);
380 4 : if (NULL != is.phone)
381 0 : mi->settings.phone = GNUNET_strdup (is.phone);
382 4 : if (NULL != is.website)
383 0 : mi->settings.website = GNUNET_strdup (is.website);
384 4 : if (NULL != is.logo)
385 0 : mi->settings.logo = GNUNET_strdup (is.logo);
386 :
387 4 : GNUNET_JSON_parse_free (spec);
388 4 : TMH_reload_instances (mi->settings.id);
389 4 : return TALER_MHD_reply_static (connection,
390 : MHD_HTTP_NO_CONTENT,
391 : NULL,
392 : NULL,
393 : 0);
394 : }
395 :
396 :
397 : MHD_RESULT
398 0 : TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
399 : struct MHD_Connection *connection,
400 : struct TMH_HandlerContext *hc)
401 : {
402 0 : struct TMH_MerchantInstance *mi = hc->instance;
403 :
404 0 : return patch_instances_ID (mi,
405 : connection,
406 : hc);
407 : }
408 :
409 :
410 : MHD_RESULT
411 4 : TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh,
412 : struct MHD_Connection *connection,
413 : struct TMH_HandlerContext *hc)
414 : {
415 : struct TMH_MerchantInstance *mi;
416 :
417 4 : mi = TMH_lookup_instance (hc->infix);
418 4 : if (NULL == mi)
419 : {
420 0 : return TALER_MHD_reply_with_error (connection,
421 : MHD_HTTP_NOT_FOUND,
422 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
423 0 : hc->infix);
424 : }
425 4 : if (mi->deleted)
426 : {
427 0 : return TALER_MHD_reply_with_error (connection,
428 : MHD_HTTP_CONFLICT,
429 : TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED,
430 0 : hc->infix);
431 : }
432 4 : return patch_instances_ID (mi,
433 : connection,
434 : hc);
435 : }
436 :
437 :
438 : /* end of taler-merchant-httpd_private-patch-instances-ID.c */
|