Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014--2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Lesser General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/backend/taler-merchant-httpd_helper.c
18 : * @brief shared logic for various handlers
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include <gnunet/gnunet_db_lib.h>
24 : #include <taler/taler_json_lib.h>
25 : #include "taler-merchant-httpd_helper.h"
26 : #include <taler/taler_templating_lib.h>
27 : #include <taler/taler_dbevents.h>
28 : #include "merchant-database/insert_pending_webhook.h"
29 : #include "merchant-database/iterate_webhooks_by_event.h"
30 : #include "merchant-database/iterate_exchange_accounts.h"
31 : #include "merchant-database/get_login_token.h"
32 : #include "merchant-database/get_unit.h"
33 : #include "merchant-database/event_notify.h"
34 :
35 :
36 : json_t *
37 114 : TMH_instance_metadata_to_json (const struct TMH_MerchantInstance *mi)
38 : {
39 114 : const struct TALER_MERCHANTDB_InstanceSettings *settings = &mi->settings;
40 114 : struct TALER_MERCHANT_MetaData md = {
41 114 : .name = settings->name,
42 114 : .website = settings->website,
43 114 : .email = settings->email,
44 114 : .logo = settings->logo,
45 114 : .address = settings->address,
46 114 : .jurisdiction = settings->jurisdiction
47 : };
48 :
49 114 : return TALER_MERCHANT_metadata_to_json (&md);
50 : }
51 :
52 :
53 : void
54 0 : TMH_quantity_defaults_from_unit (const struct TMH_MerchantInstance *mi,
55 : const char *unit,
56 : bool *allow_fractional,
57 : uint32_t *precision_level)
58 : {
59 0 : GNUNET_assert (NULL != allow_fractional);
60 0 : GNUNET_assert (NULL != precision_level);
61 0 : if (GNUNET_OK !=
62 0 : TMH_unit_defaults_for_instance (mi,
63 : unit,
64 : allow_fractional,
65 : precision_level))
66 : {
67 0 : *allow_fractional = false;
68 0 : *precision_level = 0;
69 : }
70 0 : }
71 :
72 :
73 : enum GNUNET_GenericReturnValue
74 66 : TMH_unit_defaults_for_instance (const struct TMH_MerchantInstance *mi,
75 : const char *unit,
76 : bool *allow_fractional,
77 : uint32_t *precision_level)
78 : {
79 66 : struct TALER_MERCHANTDB_UnitDetails ud = { 0 };
80 : enum GNUNET_DB_QueryStatus qs;
81 66 : bool allow = false;
82 66 : uint32_t precision = 0;
83 :
84 66 : GNUNET_assert (NULL != allow_fractional);
85 66 : GNUNET_assert (NULL != precision_level);
86 :
87 66 : qs = TALER_MERCHANTDB_get_unit (TMH_db,
88 66 : mi->settings.id,
89 : unit,
90 : &ud);
91 66 : switch (qs)
92 : {
93 0 : case GNUNET_DB_STATUS_HARD_ERROR:
94 : case GNUNET_DB_STATUS_SOFT_ERROR:
95 0 : TALER_MERCHANTDB_unit_details_free (&ud);
96 0 : return GNUNET_SYSERR;
97 6 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
98 6 : allow = ud.unit_allow_fraction;
99 6 : precision = ud.unit_precision_level;
100 6 : break;
101 60 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
102 60 : break;
103 0 : default:
104 0 : GNUNET_break (0);
105 0 : TALER_MERCHANTDB_unit_details_free (&ud);
106 0 : return GNUNET_SYSERR;
107 : }
108 66 : TALER_MERCHANTDB_unit_details_free (&ud);
109 :
110 : /* This is definitely not supposed to happen
111 : combination of allow -> false, and precision > 0
112 : yet let's fix it */
113 66 : if ( (! allow) &&
114 : (0 != precision) )
115 : {
116 0 : GNUNET_break (0);
117 0 : precision = 0;
118 : }
119 66 : *allow_fractional = allow;
120 66 : *precision_level = precision;
121 66 : return GNUNET_OK;
122 : }
123 :
124 :
125 : enum GNUNET_GenericReturnValue
126 0 : TMH_cmp_wire_account (
127 : const json_t *account,
128 : const struct TMH_WireMethod *wm)
129 : {
130 0 : const char *credit_facade_url = NULL;
131 0 : const json_t *credit_facade_credentials = NULL;
132 : struct TALER_FullPayto uri;
133 : struct GNUNET_JSON_Specification ispec[] = {
134 0 : TALER_JSON_spec_full_payto_uri ("payto_uri",
135 : &uri),
136 0 : GNUNET_JSON_spec_mark_optional (
137 : TALER_JSON_spec_web_url ("credit_facade_url",
138 : &credit_facade_url),
139 : NULL),
140 0 : GNUNET_JSON_spec_mark_optional (
141 : GNUNET_JSON_spec_object_const ("credit_facade_credentials",
142 : &credit_facade_credentials),
143 : NULL),
144 0 : GNUNET_JSON_spec_end ()
145 : };
146 : enum GNUNET_GenericReturnValue res;
147 : const char *ename;
148 : unsigned int eline;
149 :
150 0 : res = GNUNET_JSON_parse (account,
151 : ispec,
152 : &ename,
153 : &eline);
154 0 : if (GNUNET_OK != res)
155 : {
156 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
157 : "Failed to parse account spec: %s (%u)\n",
158 : ename,
159 : eline);
160 0 : return GNUNET_SYSERR;
161 : }
162 0 : if (0 !=
163 0 : TALER_full_payto_cmp (wm->payto_uri,
164 : uri))
165 : {
166 0 : return GNUNET_SYSERR;
167 : }
168 0 : if ( (NULL == credit_facade_url) !=
169 0 : (NULL == wm->credit_facade_url) ||
170 0 : (NULL == credit_facade_credentials) !=
171 0 : (NULL == wm->credit_facade_credentials) )
172 : {
173 0 : return GNUNET_NO;
174 : }
175 0 : if ( (NULL != credit_facade_url) &&
176 0 : (0 != strcmp (credit_facade_url,
177 0 : wm->credit_facade_url)) )
178 : {
179 0 : return GNUNET_NO;
180 : }
181 0 : if ( (NULL != credit_facade_credentials) &&
182 0 : (! json_equal (credit_facade_credentials,
183 0 : wm->credit_facade_credentials)) )
184 : {
185 0 : return GNUNET_NO;
186 : }
187 0 : return GNUNET_YES;
188 : }
189 :
190 :
191 : bool
192 0 : TMH_accounts_array_valid (const json_t *accounts)
193 : {
194 : size_t len;
195 :
196 0 : if (! json_is_array (accounts))
197 : {
198 0 : GNUNET_break_op (0);
199 0 : return false;
200 : }
201 0 : len = json_array_size (accounts);
202 0 : for (size_t i = 0; i<len; i++)
203 : {
204 0 : json_t *payto_uri = json_array_get (accounts,
205 : i);
206 0 : const char *credit_facade_url = NULL;
207 0 : const json_t *credit_facade_credentials = NULL;
208 : struct TALER_FullPayto uri;
209 : struct GNUNET_JSON_Specification ispec[] = {
210 0 : TALER_JSON_spec_full_payto_uri ("payto_uri",
211 : &uri),
212 0 : GNUNET_JSON_spec_mark_optional (
213 : TALER_JSON_spec_web_url ("credit_facade_url",
214 : &credit_facade_url),
215 : NULL),
216 0 : GNUNET_JSON_spec_mark_optional (
217 : GNUNET_JSON_spec_object_const ("credit_facade_credentials",
218 : &credit_facade_credentials),
219 : NULL),
220 0 : GNUNET_JSON_spec_end ()
221 : };
222 : enum GNUNET_GenericReturnValue res;
223 : const char *ename;
224 : unsigned int eline;
225 :
226 0 : res = GNUNET_JSON_parse (payto_uri,
227 : ispec,
228 : &ename,
229 : &eline);
230 0 : if (GNUNET_OK != res)
231 : {
232 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
233 : "Failed to parse account spec: %s (%u)\n",
234 : ename,
235 : eline);
236 0 : return false;
237 : }
238 :
239 : /* Test for the same payto:// URI being given twice */
240 0 : for (size_t j = 0; j<i; j++)
241 : {
242 0 : json_t *old_uri = json_array_get (accounts,
243 : j);
244 0 : if (0 == strcmp (uri.full_payto,
245 : json_string_value (
246 0 : json_object_get (old_uri,
247 : "payto_uri"))))
248 : {
249 0 : GNUNET_break_op (0);
250 0 : return false;
251 : }
252 : }
253 : {
254 : char *err;
255 :
256 0 : if (NULL !=
257 0 : (err = TALER_payto_validate (uri)))
258 : {
259 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
260 : "Encountered invalid payto://-URI `%s': %s\n",
261 : uri.full_payto,
262 : err);
263 0 : GNUNET_free (err);
264 0 : return false;
265 : }
266 : }
267 0 : if ( (NULL == credit_facade_url) !=
268 : (NULL == credit_facade_credentials) )
269 : {
270 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
271 : "If credit_facade_url is given, credit_facade_credentials must also be specified (violated for %s)\n",
272 : uri.full_payto);
273 0 : return false;
274 : }
275 0 : if ( (NULL != credit_facade_url) ||
276 0 : (NULL != credit_facade_credentials) )
277 : {
278 : struct TALER_MERCHANT_BANK_AuthenticationData auth;
279 :
280 0 : if (GNUNET_OK !=
281 0 : TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
282 : credit_facade_url,
283 : &auth))
284 : {
285 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
286 : "Invalid credit facade URL or credentials `%s'\n",
287 : credit_facade_url);
288 0 : return false;
289 : }
290 0 : TALER_MERCHANT_BANK_auth_free (&auth);
291 : }
292 : } /* end for all accounts */
293 0 : return true;
294 : }
295 :
296 :
297 : bool
298 156 : TMH_location_object_valid (const json_t *location)
299 : {
300 156 : const char *country = NULL;
301 156 : const char *subdivision = NULL;
302 156 : const char *district = NULL;
303 156 : const char *town = NULL;
304 156 : const char *town_loc = NULL;
305 156 : const char *postcode = NULL;
306 156 : const char *street = NULL;
307 156 : const char *building = NULL;
308 156 : const char *building_no = NULL;
309 156 : const json_t *lines = NULL;
310 : struct GNUNET_JSON_Specification spec[] = {
311 156 : GNUNET_JSON_spec_mark_optional (
312 : GNUNET_JSON_spec_string ("country",
313 : &country),
314 : NULL),
315 156 : GNUNET_JSON_spec_mark_optional (
316 : GNUNET_JSON_spec_string ("country_subdivision",
317 : &subdivision),
318 : NULL),
319 156 : GNUNET_JSON_spec_mark_optional (
320 : GNUNET_JSON_spec_string ("district",
321 : &district),
322 : NULL),
323 156 : GNUNET_JSON_spec_mark_optional (
324 : GNUNET_JSON_spec_string ("town",
325 : &town),
326 : NULL),
327 156 : GNUNET_JSON_spec_mark_optional (
328 : GNUNET_JSON_spec_string ("town_location",
329 : &town_loc),
330 : NULL),
331 156 : GNUNET_JSON_spec_mark_optional (
332 : GNUNET_JSON_spec_string ("post_code",
333 : &postcode),
334 : NULL),
335 156 : GNUNET_JSON_spec_mark_optional (
336 : GNUNET_JSON_spec_string ("street",
337 : &street),
338 : NULL),
339 156 : GNUNET_JSON_spec_mark_optional (
340 : GNUNET_JSON_spec_string ("building_name",
341 : &building),
342 : NULL),
343 156 : GNUNET_JSON_spec_mark_optional (
344 : GNUNET_JSON_spec_string ("building_number",
345 : &building_no),
346 : NULL),
347 156 : GNUNET_JSON_spec_mark_optional (
348 : GNUNET_JSON_spec_array_const ("address_lines",
349 : &lines),
350 : NULL),
351 156 : GNUNET_JSON_spec_end ()
352 : };
353 : const char *ename;
354 : unsigned int eline;
355 :
356 156 : if (GNUNET_OK !=
357 156 : GNUNET_JSON_parse (location,
358 : spec,
359 : &ename,
360 : &eline))
361 : {
362 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
363 : "Invalid location for field %s\n",
364 : ename);
365 0 : return false;
366 : }
367 156 : if (NULL != lines)
368 : {
369 : size_t idx;
370 : json_t *line;
371 :
372 0 : json_array_foreach (lines, idx, line)
373 : {
374 0 : if (! json_is_string (line))
375 : {
376 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
377 : "Invalid address line #%u in location\n",
378 : (unsigned int) idx);
379 0 : return false;
380 : }
381 : }
382 : }
383 156 : return true;
384 : }
385 :
386 :
387 : bool
388 108 : TMH_products_array_valid (const json_t *products)
389 : {
390 : const json_t *product;
391 : size_t idx;
392 108 : bool valid = true;
393 :
394 108 : if (! json_is_array (products))
395 : {
396 0 : GNUNET_break_op (0);
397 0 : return false;
398 : }
399 140 : json_array_foreach ((json_t *) products, idx, product)
400 : {
401 : /* FIXME: use TALER_MERCHANT_parse_product() instead? */
402 32 : const char *product_id = NULL;
403 : const char *description;
404 32 : uint64_t quantity = 0;
405 32 : bool quantity_missing = true;
406 32 : const char *unit_quantity = NULL;
407 32 : bool unit_quantity_missing = true;
408 32 : const char *unit = NULL;
409 32 : struct TALER_Amount price = { .value = 0 };
410 32 : const char *image_data_url = NULL;
411 32 : const json_t *taxes = NULL;
412 32 : struct GNUNET_TIME_Timestamp delivery_date = { 0 };
413 : struct GNUNET_JSON_Specification spec[] = {
414 32 : GNUNET_JSON_spec_mark_optional (
415 : TALER_JSON_spec_slug ("product_id",
416 : &product_id),
417 : NULL),
418 32 : TALER_JSON_spec_i18n_str ("description",
419 : &description),
420 32 : GNUNET_JSON_spec_mark_optional (
421 : GNUNET_JSON_spec_uint64 ("quantity",
422 : &quantity),
423 : &quantity_missing),
424 32 : GNUNET_JSON_spec_mark_optional (
425 : GNUNET_JSON_spec_string ("unit_quantity",
426 : &unit_quantity),
427 : &unit_quantity_missing),
428 32 : GNUNET_JSON_spec_mark_optional (
429 : TALER_JSON_spec_slug ("unit",
430 : &unit),
431 : NULL),
432 32 : GNUNET_JSON_spec_mark_optional (
433 : TALER_JSON_spec_amount_any ("price",
434 : &price),
435 : NULL),
436 32 : GNUNET_JSON_spec_mark_optional (
437 : GNUNET_JSON_spec_string ("image",
438 : &image_data_url),
439 : NULL),
440 32 : GNUNET_JSON_spec_mark_optional (
441 : GNUNET_JSON_spec_array_const ("taxes",
442 : &taxes),
443 : NULL),
444 32 : GNUNET_JSON_spec_mark_optional (
445 : GNUNET_JSON_spec_timestamp ("delivery_date",
446 : &delivery_date),
447 : NULL),
448 32 : GNUNET_JSON_spec_end ()
449 : };
450 : const char *ename;
451 : unsigned int eline;
452 :
453 32 : if (GNUNET_OK !=
454 32 : GNUNET_JSON_parse (product,
455 : spec,
456 : &ename,
457 : &eline))
458 : {
459 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
460 : "Invalid product #%u for field %s\n",
461 : (unsigned int) idx,
462 : ename);
463 0 : return false;
464 : }
465 32 : if (quantity_missing)
466 : {
467 4 : if (unit_quantity_missing)
468 : {
469 0 : GNUNET_break_op (0);
470 0 : valid = false;
471 : }
472 : }
473 32 : if ( (NULL != image_data_url) &&
474 31 : (! TALER_MERCHANT_image_data_url_valid (image_data_url)) )
475 : {
476 0 : GNUNET_break_op (0);
477 0 : valid = false;
478 : }
479 32 : if ( (NULL != taxes) &&
480 31 : (! TALER_MERCHANT_taxes_array_valid (taxes)) )
481 : {
482 0 : GNUNET_break_op (0);
483 0 : valid = false;
484 : }
485 32 : GNUNET_JSON_parse_free (spec);
486 32 : if (! valid)
487 0 : break;
488 : }
489 :
490 108 : return valid;
491 : }
492 :
493 :
494 : enum GNUNET_GenericReturnValue
495 70 : TMH_validate_unit_price_array (const struct TALER_Amount *prices,
496 : size_t prices_len)
497 : {
498 : /* Check for duplicate currencies */
499 140 : for (size_t i = 0; i < prices_len; i++)
500 : {
501 78 : for (size_t j = i + 1; j < prices_len; j++)
502 : {
503 8 : if (GNUNET_YES ==
504 8 : TALER_amount_cmp_currency (&prices[i],
505 8 : &prices[j]))
506 : {
507 : /* duplicate currency, not allowed! */
508 4 : GNUNET_break_op (0);
509 4 : return GNUNET_SYSERR;
510 : }
511 : }
512 : }
513 66 : return GNUNET_OK;
514 : }
515 :
516 :
517 : bool
518 4 : TMH_category_set_contains (const struct TMH_CategorySet *set,
519 : uint64_t id)
520 : {
521 6 : for (size_t i = 0; i < set->len; i++)
522 2 : if (set->ids[i] == id)
523 0 : return true;
524 4 : return false;
525 : }
526 :
527 :
528 : void
529 4 : TMH_category_set_add (struct TMH_CategorySet *set,
530 : uint64_t id)
531 : {
532 4 : if (TMH_category_set_contains (set,
533 : id))
534 0 : return;
535 4 : GNUNET_array_append (set->ids,
536 : set->len,
537 : id);
538 : }
539 :
540 :
541 : bool
542 10 : TMH_unit_set_contains (const struct TMH_UnitSet *set,
543 : const char *unit)
544 : {
545 14 : for (unsigned int i = 0; i < set->len; i++)
546 8 : if (0 == strcmp (set->units[i],
547 : unit))
548 4 : return true;
549 6 : return false;
550 : }
551 :
552 :
553 : void
554 10 : TMH_unit_set_add (struct TMH_UnitSet *set,
555 : const char *unit)
556 : {
557 10 : if (TMH_unit_set_contains (set,
558 : unit))
559 4 : return;
560 6 : GNUNET_array_append (set->units,
561 : set->len,
562 : GNUNET_strdup (unit));
563 : }
564 :
565 :
566 : void
567 4 : TMH_unit_set_clear (struct TMH_UnitSet *set)
568 : {
569 10 : for (unsigned int i = 0; i < set->len; i++)
570 6 : GNUNET_free (set->units[i]);
571 4 : GNUNET_free (set->units);
572 4 : set->units = NULL;
573 4 : set->len = 0;
574 4 : }
575 :
576 :
577 : struct TMH_WireMethod *
578 27 : TMH_setup_wire_account (
579 : struct TALER_FullPayto payto_uri,
580 : const char *credit_facade_url,
581 : const json_t *credit_facade_credentials)
582 : {
583 : struct TMH_WireMethod *wm;
584 : char *emsg;
585 :
586 27 : if (NULL != (emsg = TALER_payto_validate (payto_uri)))
587 : {
588 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
589 : "Invalid URI `%s': %s\n",
590 : payto_uri.full_payto,
591 : emsg);
592 0 : GNUNET_free (emsg);
593 0 : return NULL;
594 : }
595 :
596 27 : wm = GNUNET_new (struct TMH_WireMethod);
597 27 : if (NULL != credit_facade_url)
598 : wm->credit_facade_url
599 2 : = GNUNET_strdup (credit_facade_url);
600 27 : if (NULL != credit_facade_credentials)
601 : wm->credit_facade_credentials
602 2 : = json_incref ((json_t*) credit_facade_credentials);
603 27 : GNUNET_CRYPTO_random_block (&wm->wire_salt,
604 : sizeof (wm->wire_salt));
605 : wm->payto_uri.full_payto
606 27 : = GNUNET_strdup (payto_uri.full_payto);
607 27 : TALER_merchant_wire_signature_hash (payto_uri,
608 27 : &wm->wire_salt,
609 : &wm->h_wire);
610 : wm->wire_method
611 27 : = TALER_payto_get_method (payto_uri.full_payto);
612 27 : wm->active = true;
613 27 : return wm;
614 : }
615 :
616 :
617 : enum TALER_ErrorCode
618 78 : TMH_check_token (const char *token,
619 : const char *instance_id,
620 : enum TMH_AuthScope *as)
621 : {
622 : enum TMH_AuthScope scope;
623 : struct GNUNET_TIME_Timestamp expiration;
624 : enum GNUNET_DB_QueryStatus qs;
625 : struct TALER_MERCHANTDB_LoginTokenP btoken;
626 :
627 78 : if (NULL == token)
628 : {
629 8 : *as = TMH_AS_NONE;
630 8 : return TALER_EC_NONE;
631 : }
632 70 : if (0 != strncasecmp (token,
633 : RFC_8959_PREFIX,
634 : strlen (RFC_8959_PREFIX)))
635 : {
636 0 : *as = TMH_AS_NONE;
637 0 : return TALER_EC_NONE;
638 : }
639 70 : token += strlen (RFC_8959_PREFIX);
640 70 : if (GNUNET_OK !=
641 70 : GNUNET_STRINGS_string_to_data (token,
642 : strlen (token),
643 : &btoken,
644 : sizeof (btoken)))
645 : {
646 7 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
647 : "Given authorization token `%s' is malformed\n",
648 : token);
649 7 : GNUNET_break_op (0);
650 7 : return TALER_EC_GENERIC_TOKEN_MALFORMED;
651 : }
652 63 : qs = TALER_MERCHANTDB_get_login_token (TMH_db,
653 : instance_id,
654 : &btoken,
655 : &expiration,
656 : (uint32_t*) &scope);
657 63 : if (qs < 0)
658 : {
659 0 : GNUNET_break (0);
660 0 : return TALER_EC_GENERIC_DB_FETCH_FAILED;
661 : }
662 63 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
663 : {
664 9 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
665 : "Authorization token `%s' unknown\n",
666 : token);
667 9 : return TALER_EC_GENERIC_TOKEN_UNKNOWN;
668 : }
669 54 : if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
670 : {
671 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
672 : "Authorization token `%s' expired\n",
673 : token);
674 0 : return TALER_EC_GENERIC_TOKEN_EXPIRED;
675 : }
676 54 : *as = scope;
677 54 : return TALER_EC_NONE;
678 : }
679 :
680 :
681 : enum GNUNET_GenericReturnValue
682 61 : TMH_check_auth_config (struct MHD_Connection *connection,
683 : const json_t *jauth,
684 : const char **auth_password)
685 : {
686 61 : bool auth_wellformed = false;
687 61 : const char *auth_method = json_string_value (json_object_get (jauth,
688 : "method"));
689 :
690 61 : *auth_password = NULL;
691 61 : if (NULL == auth_method)
692 : {
693 0 : GNUNET_break_op (0);
694 : }
695 61 : else if ((GNUNET_YES != TMH_strict_v19) &&
696 61 : (0 == strcmp (auth_method,
697 : "external")))
698 : {
699 34 : auth_wellformed = true;
700 : }
701 27 : else if (GNUNET_YES == TMH_auth_disabled)
702 : {
703 0 : auth_wellformed = true;
704 : }
705 27 : else if (0 == strcmp (auth_method,
706 : "token"))
707 : {
708 : json_t *pw_value;
709 :
710 27 : pw_value = json_object_get (jauth,
711 : "password");
712 27 : if (NULL == pw_value)
713 : {
714 0 : pw_value = json_object_get (jauth,
715 : "token");
716 : }
717 27 : if (NULL == pw_value)
718 : {
719 0 : auth_wellformed = false;
720 0 : GNUNET_break_op (0);
721 : }
722 : else
723 : {
724 27 : *auth_password = json_string_value (pw_value);
725 27 : if (NULL != *auth_password)
726 : {
727 27 : if (0 == strncasecmp (RFC_8959_PREFIX,
728 : *auth_password,
729 : strlen (RFC_8959_PREFIX)))
730 : {
731 4 : *auth_password = *auth_password + strlen (RFC_8959_PREFIX);
732 : }
733 27 : auth_wellformed = true;
734 : }
735 : }
736 : }
737 :
738 61 : if (! auth_wellformed)
739 : {
740 0 : GNUNET_break_op (0);
741 : return (MHD_YES ==
742 0 : TALER_MHD_reply_with_error (connection,
743 : MHD_HTTP_BAD_REQUEST,
744 : TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH,
745 : "bad authentication config"))
746 : ? GNUNET_NO
747 0 : : GNUNET_SYSERR;
748 : }
749 61 : return GNUNET_OK;
750 : }
751 :
752 :
753 : void
754 17 : TMH_uuid_from_string (const char *uuids,
755 : struct GNUNET_Uuid *uuid)
756 : {
757 : struct GNUNET_HashCode hc;
758 :
759 : GNUNET_static_assert (sizeof (hc) > sizeof (*uuid));
760 17 : GNUNET_CRYPTO_hash (uuids,
761 : strlen (uuids),
762 : &hc);
763 17 : GNUNET_memcpy (uuid,
764 : &hc,
765 : sizeof (*uuid));
766 17 : }
767 :
768 :
769 : /**
770 : * Closure for #trigger_webhook_cb.
771 : *
772 : * @param instance which is the instance we work with
773 : * @param root JSON data to fill into the template
774 : * @param rv, query of the TALER_TEMPLATEING_fill
775 : */
776 : struct Trigger
777 : {
778 : const char *instance;
779 :
780 : const json_t *root;
781 :
782 : enum GNUNET_DB_QueryStatus rv;
783 :
784 : };
785 :
786 : /**
787 : * Typically called by `TMH_trigger_webhook`.
788 : *
789 : * @param[in,out] cls a `struct Trigger` with information about the webhook
790 : * @param webhook_serial reference to the configured webhook template.
791 : * @param event_type is the event/action of the webhook
792 : * @param url to make request to
793 : * @param http_method use for the webhook
794 : * @param header_template of the webhook
795 : * @param body_template of the webhook
796 : */
797 : static void
798 4 : trigger_webhook_cb (void *cls,
799 : uint64_t webhook_serial,
800 : const char *event_type,
801 : const char *url,
802 : const char *http_method,
803 : const char *header_template,
804 : const char *body_template)
805 : {
806 4 : struct Trigger *t = cls;
807 4 : void *header = NULL;
808 4 : void *body = NULL;
809 : size_t header_size;
810 : size_t body_size;
811 :
812 4 : if (NULL != header_template)
813 : {
814 : int ret;
815 :
816 4 : ret = TALER_TEMPLATING_fill (header_template,
817 : t->root,
818 : &header,
819 : &header_size);
820 4 : if (0 != ret)
821 : {
822 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
823 : "Failed to expand webhook header template for webhook %llu (%d)\n",
824 : (unsigned long long) webhook_serial,
825 : ret);
826 0 : t->rv = GNUNET_DB_STATUS_HARD_ERROR;
827 0 : return;
828 : }
829 : /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */
830 4 : GNUNET_assert ('\0' == ((const char *) header)[header_size]);
831 : }
832 4 : if (NULL != body_template)
833 : {
834 : int ret;
835 :
836 4 : ret = TALER_TEMPLATING_fill (body_template,
837 : t->root,
838 : &body,
839 : &body_size);
840 4 : if (0 != ret)
841 : {
842 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
843 : "Failed to expand webhook body template for webhook %llu (%d)\n",
844 : (unsigned long long) webhook_serial,
845 : ret);
846 0 : t->rv = GNUNET_DB_STATUS_HARD_ERROR;
847 0 : return;
848 : }
849 : /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */
850 4 : GNUNET_assert ('\0' == ((const char *) body)[body_size]);
851 : }
852 4 : t->rv = TALER_MERCHANTDB_insert_pending_webhook (TMH_db,
853 : t->instance,
854 : webhook_serial,
855 : url,
856 : http_method,
857 : header,
858 : body);
859 4 : if (t->rv > 0)
860 : {
861 4 : struct GNUNET_DB_EventHeaderP es = {
862 4 : .size = htons (sizeof(es)),
863 4 : .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING)
864 : };
865 4 : const void *extra = NULL;
866 4 : size_t extra_size = 0;
867 4 : TALER_MERCHANTDB_event_notify (TMH_db,
868 : &es,
869 : &extra,
870 : extra_size);
871 : }
872 : /* allocated by mustach, hence use free(), not GNUNET_free() */
873 4 : free (header);
874 4 : free (body);
875 : }
876 :
877 :
878 : /**
879 : * TMH_trigger_webhook is a function that need to be use when someone
880 : * pay. Merchant need to have a notification.
881 : *
882 : * @param instance that we need to send the webhook as a notification
883 : * @param event of the webhook
884 : * @param args argument of the function
885 : */
886 : enum GNUNET_DB_QueryStatus
887 154 : TMH_trigger_webhook (const char *instance,
888 : const char *event,
889 : const json_t *args)
890 : {
891 154 : struct Trigger t = {
892 : .instance = instance,
893 : .root = args
894 : };
895 : enum GNUNET_DB_QueryStatus qs;
896 :
897 154 : qs = TALER_MERCHANTDB_iterate_webhooks_by_event (TMH_db,
898 : instance,
899 : event,
900 : &trigger_webhook_cb,
901 : &t);
902 154 : if (qs < 0)
903 0 : return qs;
904 154 : return t.rv;
905 : }
906 :
907 :
908 : enum GNUNET_GenericReturnValue
909 207 : TMH_base_url_by_connection (struct MHD_Connection *connection,
910 : const char *instance,
911 : struct GNUNET_Buffer *buf)
912 : {
913 : const char *host;
914 : const char *forwarded_host;
915 : const char *forwarded_port;
916 : const char *uri_path;
917 :
918 207 : memset (buf,
919 : 0,
920 : sizeof (*buf));
921 207 : if (NULL != TMH_base_url)
922 : {
923 207 : GNUNET_buffer_write_str (buf,
924 : TMH_base_url);
925 : }
926 : else
927 : {
928 0 : if (GNUNET_YES ==
929 0 : TALER_mhd_is_https (connection))
930 0 : GNUNET_buffer_write_str (buf,
931 : "https://");
932 : else
933 0 : GNUNET_buffer_write_str (buf,
934 : "http://");
935 0 : host = MHD_lookup_connection_value (connection,
936 : MHD_HEADER_KIND,
937 : MHD_HTTP_HEADER_HOST);
938 0 : forwarded_host = MHD_lookup_connection_value (connection,
939 : MHD_HEADER_KIND,
940 : "X-Forwarded-Host");
941 0 : if (NULL != forwarded_host)
942 : {
943 0 : GNUNET_buffer_write_str (buf,
944 : forwarded_host);
945 : }
946 : else
947 : {
948 0 : if (NULL == host)
949 : {
950 0 : GNUNET_buffer_clear (buf);
951 0 : GNUNET_break (0);
952 0 : return GNUNET_SYSERR;
953 : }
954 0 : GNUNET_buffer_write_str (buf,
955 : host);
956 : }
957 0 : forwarded_port = MHD_lookup_connection_value (connection,
958 : MHD_HEADER_KIND,
959 : "X-Forwarded-Port");
960 0 : if (NULL != forwarded_port)
961 : {
962 0 : GNUNET_buffer_write_str (buf,
963 : ":");
964 0 : GNUNET_buffer_write_str (buf,
965 : forwarded_port);
966 : }
967 0 : uri_path = MHD_lookup_connection_value (connection,
968 : MHD_HEADER_KIND,
969 : "X-Forwarded-Prefix");
970 0 : if (NULL != uri_path)
971 0 : GNUNET_buffer_write_path (buf,
972 : uri_path);
973 : }
974 207 : if (0 != strcmp (instance,
975 : "admin"))
976 : {
977 13 : GNUNET_buffer_write_path (buf,
978 : "/instances/");
979 13 : GNUNET_buffer_write_str (buf,
980 : instance);
981 : }
982 207 : return GNUNET_OK;
983 : }
984 :
985 :
986 : enum GNUNET_GenericReturnValue
987 48 : TMH_taler_uri_by_connection (struct MHD_Connection *connection,
988 : const char *method,
989 : const char *instance,
990 : struct GNUNET_Buffer *buf)
991 : {
992 : const char *host;
993 : const char *forwarded_host;
994 : const char *forwarded_port;
995 : const char *uri_path;
996 :
997 48 : memset (buf,
998 : 0,
999 : sizeof (*buf));
1000 48 : host = MHD_lookup_connection_value (connection,
1001 : MHD_HEADER_KIND,
1002 : "Host");
1003 48 : forwarded_host = MHD_lookup_connection_value (connection,
1004 : MHD_HEADER_KIND,
1005 : "X-Forwarded-Host");
1006 48 : forwarded_port = MHD_lookup_connection_value (connection,
1007 : MHD_HEADER_KIND,
1008 : "X-Forwarded-Port");
1009 48 : uri_path = MHD_lookup_connection_value (connection,
1010 : MHD_HEADER_KIND,
1011 : "X-Forwarded-Prefix");
1012 48 : if (NULL != forwarded_host)
1013 0 : host = forwarded_host;
1014 48 : if (NULL == host)
1015 : {
1016 0 : GNUNET_break (0);
1017 0 : return GNUNET_SYSERR;
1018 : }
1019 48 : GNUNET_buffer_write_str (buf,
1020 : "taler");
1021 48 : if (GNUNET_NO == TALER_mhd_is_https (connection))
1022 48 : GNUNET_buffer_write_str (buf,
1023 : "+http");
1024 48 : GNUNET_buffer_write_str (buf,
1025 : "://");
1026 48 : GNUNET_buffer_write_str (buf,
1027 : method);
1028 48 : GNUNET_buffer_write_str (buf,
1029 : "/");
1030 48 : GNUNET_buffer_write_str (buf,
1031 : host);
1032 48 : if (NULL != forwarded_port)
1033 : {
1034 0 : GNUNET_buffer_write_str (buf,
1035 : ":");
1036 0 : GNUNET_buffer_write_str (buf,
1037 : forwarded_port);
1038 : }
1039 48 : if (NULL != uri_path)
1040 0 : GNUNET_buffer_write_path (buf,
1041 : uri_path);
1042 48 : if (0 != strcmp ("admin",
1043 : instance))
1044 : {
1045 3 : GNUNET_buffer_write_path (buf,
1046 : "instances");
1047 3 : GNUNET_buffer_write_path (buf,
1048 : instance);
1049 : }
1050 48 : return GNUNET_OK;
1051 : }
1052 :
1053 :
1054 : /**
1055 : * Closure for #add_matching_account().
1056 : */
1057 : struct ExchangeMatchContext
1058 : {
1059 : /**
1060 : * Wire method to match, NULL for all.
1061 : */
1062 : const char *wire_method;
1063 :
1064 : /**
1065 : * Array of accounts to return.
1066 : */
1067 : json_t *accounts;
1068 : };
1069 :
1070 :
1071 : /**
1072 : * If the given account is feasible, add it to the array
1073 : * of accounts we return.
1074 : *
1075 : * @param cls a `struct PostReserveContext`
1076 : * @param payto_uri URI of the account
1077 : * @param conversion_url URL of a conversion service
1078 : * @param debit_restrictions restrictions for debits from account
1079 : * @param credit_restrictions restrictions for credits to account
1080 : * @param master_sig signature affirming the account
1081 : */
1082 : static void
1083 0 : add_matching_account (
1084 : void *cls,
1085 : struct TALER_FullPayto payto_uri,
1086 : const char *conversion_url,
1087 : const json_t *debit_restrictions,
1088 : const json_t *credit_restrictions,
1089 : const struct TALER_MasterSignatureP *master_sig)
1090 : {
1091 0 : struct ExchangeMatchContext *rc = cls;
1092 : char *method;
1093 :
1094 0 : method = TALER_payto_get_method (payto_uri.full_payto);
1095 0 : if ( (NULL == rc->wire_method) ||
1096 0 : (0 == strcmp (method,
1097 : rc->wire_method)) )
1098 : {
1099 : json_t *acc;
1100 :
1101 0 : acc = GNUNET_JSON_PACK (
1102 : TALER_JSON_pack_full_payto ("payto_uri",
1103 : payto_uri),
1104 : GNUNET_JSON_pack_data_auto ("master_sig",
1105 : master_sig),
1106 : GNUNET_JSON_pack_allow_null (
1107 : GNUNET_JSON_pack_string ("conversion_url",
1108 : conversion_url)),
1109 : GNUNET_JSON_pack_array_incref ("credit_restrictions",
1110 : (json_t *) credit_restrictions),
1111 : GNUNET_JSON_pack_array_incref ("debit_restrictions",
1112 : (json_t *) debit_restrictions)
1113 : );
1114 0 : GNUNET_assert (0 ==
1115 : json_array_append_new (rc->accounts,
1116 : acc));
1117 : }
1118 0 : GNUNET_free (method);
1119 0 : }
1120 :
1121 :
1122 : /**
1123 : * Return JSON array with all of the exchange accounts
1124 : * that support the given @a wire_method.
1125 : *
1126 : * @param master_pub master public key to match exchange by
1127 : * @param wire_method NULL for any
1128 : * @return JSON array with information about all matching accounts
1129 : */
1130 : json_t *
1131 0 : TMH_exchange_accounts_by_method (
1132 : const struct TALER_MasterPublicKeyP *master_pub,
1133 : const char *wire_method)
1134 : {
1135 0 : struct ExchangeMatchContext emc = {
1136 : .wire_method = wire_method,
1137 0 : .accounts = json_array ()
1138 : };
1139 : enum GNUNET_DB_QueryStatus qs;
1140 :
1141 0 : GNUNET_assert (NULL != emc.accounts);
1142 0 : qs = TALER_MERCHANTDB_iterate_exchange_accounts (TMH_db,
1143 : master_pub,
1144 : &add_matching_account,
1145 : &emc);
1146 0 : if (qs < 0)
1147 : {
1148 0 : json_decref (emc.accounts);
1149 0 : return NULL;
1150 : }
1151 0 : return emc.accounts;
1152 : }
1153 :
1154 :
1155 : char *
1156 94 : TMH_make_order_status_url (struct MHD_Connection *con,
1157 : const char *order_id,
1158 : const char *session_id,
1159 : const char *instance_id,
1160 : struct TALER_ClaimTokenP *claim_token,
1161 : struct TALER_PrivateContractHashP *h_contract)
1162 : {
1163 : struct GNUNET_Buffer buf;
1164 : /* Number of query parameters written so far */
1165 94 : unsigned int num_qp = 0;
1166 :
1167 94 : GNUNET_assert (NULL != instance_id);
1168 94 : GNUNET_assert (NULL != order_id);
1169 94 : if (GNUNET_OK !=
1170 94 : TMH_base_url_by_connection (con,
1171 : instance_id,
1172 : &buf))
1173 : {
1174 0 : GNUNET_break (0);
1175 0 : return NULL;
1176 : }
1177 94 : GNUNET_buffer_write_path (&buf,
1178 : "/orders");
1179 94 : GNUNET_buffer_write_path (&buf,
1180 : order_id);
1181 94 : if ( (NULL != claim_token) &&
1182 93 : (! GNUNET_is_zero (claim_token)) )
1183 : {
1184 : /* 'token=' for human readability */
1185 77 : GNUNET_buffer_write_str (&buf,
1186 : "?token=");
1187 77 : GNUNET_buffer_write_data_encoded (&buf,
1188 : (char *) claim_token,
1189 : sizeof (*claim_token));
1190 77 : num_qp++;
1191 : }
1192 :
1193 94 : if (NULL != session_id)
1194 : {
1195 20 : if (num_qp > 0)
1196 14 : GNUNET_buffer_write_str (&buf,
1197 : "&session_id=");
1198 : else
1199 6 : GNUNET_buffer_write_str (&buf,
1200 : "?session_id=");
1201 20 : GNUNET_buffer_write_str (&buf,
1202 : session_id);
1203 20 : num_qp++;
1204 : }
1205 :
1206 94 : if (NULL != h_contract)
1207 : {
1208 4 : if (num_qp > 0)
1209 3 : GNUNET_buffer_write_str (&buf,
1210 : "&h_contract=");
1211 : else
1212 1 : GNUNET_buffer_write_str (&buf,
1213 : "?h_contract=");
1214 4 : GNUNET_buffer_write_data_encoded (&buf,
1215 : (char *) h_contract,
1216 : sizeof (*h_contract));
1217 : }
1218 :
1219 94 : return GNUNET_buffer_reap_str (&buf);
1220 : }
1221 :
1222 :
1223 : char *
1224 38 : TMH_make_taler_pay_uri (struct MHD_Connection *con,
1225 : const char *order_id,
1226 : const char *session_id,
1227 : const char *instance_id,
1228 : struct TALER_ClaimTokenP *claim_token)
1229 : {
1230 : struct GNUNET_Buffer buf;
1231 :
1232 38 : GNUNET_assert (NULL != instance_id);
1233 38 : GNUNET_assert (NULL != order_id);
1234 38 : if (GNUNET_OK !=
1235 38 : TMH_taler_uri_by_connection (con,
1236 : "pay",
1237 : instance_id,
1238 : &buf))
1239 : {
1240 0 : GNUNET_break (0);
1241 0 : return NULL;
1242 : }
1243 38 : GNUNET_buffer_write_path (&buf,
1244 : order_id);
1245 38 : GNUNET_buffer_write_path (&buf,
1246 : (NULL == session_id)
1247 : ? ""
1248 : : session_id);
1249 38 : if ( (NULL != claim_token) &&
1250 38 : (! GNUNET_is_zero (claim_token)))
1251 : {
1252 : /* Just 'c=' because this goes into QR
1253 : codes, so this is more compact. */
1254 24 : GNUNET_buffer_write_str (&buf,
1255 : "?c=");
1256 24 : GNUNET_buffer_write_data_encoded (&buf,
1257 : (char *) claim_token,
1258 : sizeof (struct TALER_ClaimTokenP));
1259 : }
1260 :
1261 38 : return GNUNET_buffer_reap_str (&buf);
1262 : }
1263 :
1264 :
1265 : enum GNUNET_GenericReturnValue
1266 16 : TMH_compute_order_total (const json_t *contract_terms,
1267 : int16_t choice_index,
1268 : struct TALER_Amount *total)
1269 : {
1270 16 : enum TALER_MERCHANT_ContractVersion version
1271 : = TALER_MERCHANT_CONTRACT_VERSION_0;
1272 16 : const json_t *amount_bearer = contract_terms;
1273 : struct TALER_Amount amount;
1274 : const json_t *amount_external;
1275 : struct GNUNET_JSON_Specification spec[] = {
1276 16 : GNUNET_JSON_spec_mark_optional (
1277 : TALER_MERCHANT_spec_contract_version ("version",
1278 : &version),
1279 : NULL),
1280 16 : GNUNET_JSON_spec_end ()
1281 : };
1282 :
1283 16 : if (GNUNET_OK !=
1284 16 : GNUNET_JSON_parse (contract_terms,
1285 : spec,
1286 : NULL,
1287 : NULL))
1288 : {
1289 0 : GNUNET_break (0);
1290 0 : return GNUNET_SYSERR;
1291 : }
1292 16 : if (TALER_MERCHANT_CONTRACT_VERSION_1 == version)
1293 : {
1294 0 : if (0 > choice_index)
1295 : {
1296 : /* Without a selected choice the total is undefined; the caller
1297 : must only ask for orders where a choice was picked. */
1298 0 : GNUNET_break (0);
1299 0 : return GNUNET_SYSERR;
1300 : }
1301 0 : amount_bearer = json_array_get (
1302 0 : json_object_get (contract_terms,
1303 : "choices"),
1304 : (size_t) choice_index);
1305 0 : if (NULL == amount_bearer)
1306 : {
1307 0 : GNUNET_break (0);
1308 0 : return GNUNET_SYSERR;
1309 : }
1310 : }
1311 : {
1312 : struct GNUNET_JSON_Specification aspec[] = {
1313 16 : TALER_JSON_spec_amount_any ("amount",
1314 : &amount),
1315 16 : GNUNET_JSON_spec_end ()
1316 : };
1317 :
1318 16 : if (GNUNET_OK !=
1319 16 : GNUNET_JSON_parse (amount_bearer,
1320 : aspec,
1321 : NULL,
1322 : NULL))
1323 : {
1324 0 : GNUNET_break (0);
1325 0 : return GNUNET_SYSERR;
1326 : }
1327 : }
1328 16 : *total = amount;
1329 16 : amount_external = json_object_get (contract_terms,
1330 : "amount_external");
1331 : {
1332 : size_t idx;
1333 : json_t *entry;
1334 :
1335 24 : json_array_foreach ((json_t *) amount_external, idx, entry)
1336 : {
1337 : struct TALER_Amount ext_amount;
1338 : struct GNUNET_JSON_Specification espec[] = {
1339 8 : TALER_JSON_spec_amount_any ("amount",
1340 : &ext_amount),
1341 8 : GNUNET_JSON_spec_end ()
1342 : };
1343 :
1344 8 : if (GNUNET_OK !=
1345 8 : GNUNET_JSON_parse (entry,
1346 : espec,
1347 : NULL,
1348 : NULL))
1349 : {
1350 0 : GNUNET_break (0);
1351 0 : return GNUNET_SYSERR;
1352 : }
1353 8 : if (0 >
1354 8 : TALER_amount_add (total,
1355 : total,
1356 : &ext_amount))
1357 : {
1358 0 : GNUNET_break (0);
1359 0 : return GNUNET_SYSERR;
1360 : }
1361 : }
1362 : }
1363 16 : return GNUNET_OK;
1364 : }
|