Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023-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 2.1, 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 Lesser General Public License for more details.
12 :
13 : You should have received a copy of the GNU Lesser General Public License along with
14 : TALER; see the file COPYING.LGPL. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file src/lib/merchant_api_get-private-kyc.c
19 : * @brief Implementation of the GET /private/kyc request
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <curl/curl.h>
24 : #include <jansson.h>
25 : #include <microhttpd.h> /* just for HTTP status codes */
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include <taler/merchant/get-private-kyc.h>
29 : #include "merchant_api_curl_defaults.h"
30 : #include <taler/taler_json_lib.h>
31 :
32 :
33 : /**
34 : * Maximum length of the KYC arrays supported.
35 : */
36 : #define MAX_KYC 1024
37 :
38 :
39 : /**
40 : * Handle for a GET /private/kyc operation.
41 : */
42 : struct TALER_MERCHANT_GetPrivateKycHandle
43 : {
44 : /**
45 : * Base URL of the merchant backend.
46 : */
47 : char *base_url;
48 :
49 : /**
50 : * The full URL for this request.
51 : */
52 : char *url;
53 :
54 : /**
55 : * Handle for the request.
56 : */
57 : struct GNUNET_CURL_Job *job;
58 :
59 : /**
60 : * Function to call with the result.
61 : */
62 : TALER_MERCHANT_GetPrivateKycCallback cb;
63 :
64 : /**
65 : * Closure for @a cb.
66 : */
67 : TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls;
68 :
69 : /**
70 : * Reference to the execution context.
71 : */
72 : struct GNUNET_CURL_Context *ctx;
73 :
74 : /**
75 : * Hash of the wire account to filter by, or NULL.
76 : */
77 : const struct TALER_MerchantWireHashP *h_wire;
78 :
79 : /**
80 : * Storage for the h_wire value (if set).
81 : */
82 : struct TALER_MerchantWireHashP h_wire_val;
83 :
84 : /**
85 : * True if @e h_wire was set.
86 : */
87 : bool have_h_wire;
88 :
89 : /**
90 : * Exchange URL filter, or NULL.
91 : */
92 : char *exchange_url;
93 :
94 : /**
95 : * Long-poll target.
96 : */
97 : enum TALER_EXCHANGE_KycLongPollTarget lpt;
98 :
99 : /**
100 : * Long polling timeout.
101 : */
102 : struct GNUNET_TIME_Relative timeout;
103 :
104 : /**
105 : * Instance ID for management mode, or NULL.
106 : */
107 : char *instance_id;
108 :
109 : /**
110 : * Long-poll status filter, or NULL.
111 : */
112 : char *lp_status;
113 :
114 : /**
115 : * Long-poll negated status filter, or NULL.
116 : */
117 : char *lp_not_status;
118 :
119 : /**
120 : * Long-poll ETag to suppress unchanged responses.
121 : */
122 : struct GNUNET_ShortHashCode lp_not_etag;
123 :
124 : /**
125 : * True if @e lp_not_etag was set.
126 : */
127 : bool have_lp_not_etag;
128 : };
129 :
130 :
131 : /**
132 : * Parse @a jkyc response and call the continuation on success.
133 : *
134 : * @param kyc operation handle
135 : * @param[in,out] kr response details
136 : * @param jkyc array from the reply
137 : * @return #GNUNET_OK on success (callback was called)
138 : */
139 : static enum GNUNET_GenericReturnValue
140 7 : parse_kyc (struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
141 : struct TALER_MERCHANT_GetPrivateKycResponse *kr,
142 : const json_t *jkyc)
143 : {
144 7 : unsigned int num_kycs = (unsigned int) json_array_size (jkyc);
145 7 : unsigned int num_limits = 0;
146 7 : unsigned int num_kycauths = 0;
147 7 : unsigned int pos_limits = 0;
148 7 : unsigned int pos_kycauths = 0;
149 :
150 7 : if ( (json_array_size (jkyc) != (size_t) num_kycs) ||
151 : (num_kycs > MAX_KYC) )
152 : {
153 0 : GNUNET_break_op (0);
154 0 : return GNUNET_SYSERR;
155 : }
156 :
157 14 : for (unsigned int i = 0; i<num_kycs; i++)
158 : {
159 7 : const json_t *jlimits = NULL;
160 7 : const json_t *jkycauths = NULL;
161 : struct GNUNET_JSON_Specification spec[] = {
162 7 : GNUNET_JSON_spec_mark_optional (
163 : GNUNET_JSON_spec_array_const (
164 : "limits",
165 : &jlimits),
166 : NULL),
167 7 : GNUNET_JSON_spec_mark_optional (
168 : GNUNET_JSON_spec_array_const (
169 : "payto_kycauths",
170 : &jkycauths),
171 : NULL),
172 7 : GNUNET_JSON_spec_end ()
173 : };
174 :
175 7 : if (GNUNET_OK !=
176 7 : GNUNET_JSON_parse (json_array_get (jkyc,
177 : i),
178 : spec,
179 : NULL, NULL))
180 : {
181 0 : GNUNET_break_op (0);
182 0 : return GNUNET_SYSERR;
183 : }
184 7 : num_limits += json_array_size (jlimits);
185 7 : num_kycauths += json_array_size (jkycauths);
186 7 : if ( (num_limits > MAX_KYC) ||
187 : (num_kycauths > MAX_KYC) )
188 : {
189 : /* Bound the stack VLAs declared below by an untrusted response. */
190 0 : GNUNET_break_op (0);
191 0 : return GNUNET_SYSERR;
192 : }
193 : }
194 :
195 7 : {
196 7 : struct TALER_MERCHANT_GetPrivateKycRedirectDetail kycs[
197 7 : GNUNET_NZL (num_kycs)];
198 7 : struct TALER_EXCHANGE_AccountLimit limits[
199 7 : GNUNET_NZL (num_limits)];
200 7 : struct TALER_FullPayto payto_kycauths[
201 7 : GNUNET_NZL (num_kycauths)];
202 :
203 7 : memset (kycs,
204 : 0,
205 : sizeof (kycs));
206 14 : for (unsigned int i = 0; i<num_kycs; i++)
207 : {
208 7 : struct TALER_MERCHANT_GetPrivateKycRedirectDetail *rd
209 : = &kycs[i];
210 7 : const json_t *jlimits = NULL;
211 7 : const json_t *jkycauths = NULL;
212 : uint32_t hs;
213 : struct GNUNET_JSON_Specification spec[] = {
214 7 : TALER_JSON_spec_full_payto_uri (
215 : "payto_uri",
216 : &rd->payto_uri),
217 7 : TALER_JSON_spec_web_url (
218 : "exchange_url",
219 : &rd->exchange_url),
220 7 : GNUNET_JSON_spec_uint32 (
221 : "exchange_http_status",
222 : &hs),
223 7 : GNUNET_JSON_spec_bool (
224 : "no_keys",
225 : &rd->no_keys),
226 7 : GNUNET_JSON_spec_bool (
227 : "auth_conflict",
228 : &rd->auth_conflict),
229 7 : GNUNET_JSON_spec_mark_optional (
230 : TALER_JSON_spec_ec (
231 : "exchange_code",
232 : &rd->exchange_code),
233 : NULL),
234 7 : GNUNET_JSON_spec_mark_optional (
235 7 : GNUNET_JSON_spec_fixed_auto (
236 : "access_token",
237 : &rd->access_token),
238 : &rd->no_access_token),
239 7 : GNUNET_JSON_spec_fixed_auto (
240 : "h_wire",
241 : &rd->h_wire),
242 7 : GNUNET_JSON_spec_mark_optional (
243 : GNUNET_JSON_spec_string (
244 : "status",
245 : &rd->status),
246 : NULL),
247 : /* Mandatory since **v25** */
248 7 : GNUNET_JSON_spec_mark_optional (
249 : GNUNET_JSON_spec_string (
250 : "exchange_currency",
251 : &rd->exchange_currency),
252 : NULL),
253 7 : GNUNET_JSON_spec_mark_optional (
254 : GNUNET_JSON_spec_array_const (
255 : "limits",
256 : &jlimits),
257 : NULL),
258 7 : GNUNET_JSON_spec_mark_optional (
259 : GNUNET_JSON_spec_array_const (
260 : "payto_kycauths",
261 : &jkycauths),
262 : NULL),
263 7 : GNUNET_JSON_spec_end ()
264 : };
265 : size_t j;
266 : json_t *jlimit;
267 : json_t *jkycauth;
268 :
269 7 : if (GNUNET_OK !=
270 7 : GNUNET_JSON_parse (json_array_get (jkyc,
271 : i),
272 : spec,
273 : NULL, NULL))
274 : {
275 0 : GNUNET_break_op (0);
276 0 : return GNUNET_SYSERR;
277 : }
278 7 : rd->exchange_http_status = (unsigned int) hs;
279 7 : rd->limits = &limits[pos_limits];
280 7 : rd->limits_length = json_array_size (jlimits);
281 10 : json_array_foreach (jlimits, j, jlimit)
282 : {
283 3 : struct TALER_EXCHANGE_AccountLimit *limit
284 : = &limits[pos_limits];
285 : struct GNUNET_JSON_Specification jspec[] = {
286 3 : TALER_JSON_spec_kycte (
287 : "operation_type",
288 : &limit->operation_type),
289 3 : GNUNET_JSON_spec_relative_time (
290 : "timeframe",
291 : &limit->timeframe),
292 3 : TALER_JSON_spec_amount_any (
293 : "threshold",
294 : &limit->threshold),
295 3 : GNUNET_JSON_spec_mark_optional (
296 : GNUNET_JSON_spec_bool (
297 : "soft_limit",
298 : &limit->soft_limit),
299 : NULL),
300 3 : GNUNET_JSON_spec_end ()
301 : };
302 :
303 3 : GNUNET_assert (pos_limits < num_limits);
304 3 : limit->soft_limit = false;
305 3 : if (GNUNET_OK !=
306 3 : GNUNET_JSON_parse (jlimit,
307 : jspec,
308 : NULL, NULL))
309 : {
310 0 : GNUNET_break_op (0);
311 0 : return GNUNET_SYSERR;
312 : }
313 3 : pos_limits++;
314 : }
315 7 : rd->payto_kycauths = &payto_kycauths[pos_kycauths];
316 7 : rd->pkycauth_length = json_array_size (jkycauths);
317 8 : json_array_foreach (jkycauths, j, jkycauth)
318 : {
319 1 : GNUNET_assert (pos_kycauths < num_kycauths);
320 : payto_kycauths[pos_kycauths].full_payto
321 1 : = (char *) json_string_value (jkycauth);
322 1 : if (NULL == payto_kycauths[pos_kycauths].full_payto)
323 : {
324 0 : GNUNET_break_op (0);
325 0 : return GNUNET_SYSERR;
326 : }
327 1 : pos_kycauths++;
328 : }
329 : }
330 7 : kr->details.ok.kycs = kycs;
331 7 : kr->details.ok.kycs_length = num_kycs;
332 7 : kyc->cb (kyc->cb_cls,
333 : kr);
334 7 : kyc->cb = NULL;
335 : }
336 7 : return GNUNET_OK;
337 : }
338 :
339 :
340 : /**
341 : * Function called when we're done processing the
342 : * HTTP GET /private/kyc request.
343 : *
344 : * @param cls the `struct TALER_MERCHANT_GetPrivateKycHandle`
345 : * @param response_code HTTP response code, 0 on error
346 : * @param response response body, NULL if not in JSON
347 : */
348 : static void
349 9 : handle_get_kyc_finished (void *cls,
350 : long response_code,
351 : const void *response)
352 : {
353 9 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc = cls;
354 9 : const json_t *json = response;
355 9 : struct TALER_MERCHANT_GetPrivateKycResponse kr = {
356 9 : .hr.http_status = (unsigned int) response_code,
357 : .hr.reply = json
358 : };
359 :
360 9 : kyc->job = NULL;
361 9 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
362 : "Got /private/kyc response with status code %u\n",
363 : (unsigned int) response_code);
364 9 : switch (response_code)
365 : {
366 7 : case MHD_HTTP_OK:
367 : {
368 : const json_t *jkyc;
369 : struct GNUNET_JSON_Specification spec[] = {
370 7 : GNUNET_JSON_spec_array_const ("kyc_data",
371 : &jkyc),
372 7 : GNUNET_JSON_spec_end ()
373 : };
374 :
375 7 : if (GNUNET_OK !=
376 7 : GNUNET_JSON_parse (json,
377 : spec,
378 : NULL, NULL))
379 : {
380 0 : kr.hr.http_status = 0;
381 0 : kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
382 0 : break;
383 : }
384 7 : if (GNUNET_OK !=
385 7 : parse_kyc (kyc,
386 : &kr,
387 : jkyc))
388 : {
389 0 : kr.hr.http_status = 0;
390 0 : kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
391 0 : break;
392 : }
393 : /* parse_kyc called the continuation already */
394 7 : TALER_MERCHANT_get_private_kyc_cancel (kyc);
395 7 : return;
396 : }
397 2 : case MHD_HTTP_NO_CONTENT:
398 2 : break;
399 0 : case MHD_HTTP_NOT_MODIFIED:
400 : /* ETag matched; nothing changed. No body expected. */
401 0 : break;
402 0 : case MHD_HTTP_BAD_REQUEST:
403 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
404 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
405 0 : break;
406 0 : case MHD_HTTP_UNAUTHORIZED:
407 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
408 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
409 0 : break;
410 0 : case MHD_HTTP_NOT_FOUND:
411 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
412 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
413 0 : break;
414 0 : case MHD_HTTP_NOT_ACCEPTABLE:
415 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
416 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
417 0 : break;
418 0 : case MHD_HTTP_SERVICE_UNAVAILABLE:
419 0 : break;
420 0 : default:
421 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
422 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
423 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
424 : "Unexpected response code %u/%d\n",
425 : (unsigned int) response_code,
426 : (int) kr.hr.ec);
427 0 : break;
428 : }
429 2 : kyc->cb (kyc->cb_cls,
430 : &kr);
431 2 : TALER_MERCHANT_get_private_kyc_cancel (kyc);
432 : }
433 :
434 :
435 : struct TALER_MERCHANT_GetPrivateKycHandle *
436 9 : TALER_MERCHANT_get_private_kyc_create (
437 : struct GNUNET_CURL_Context *ctx,
438 : const char *url)
439 : {
440 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc;
441 :
442 9 : kyc = GNUNET_new (struct TALER_MERCHANT_GetPrivateKycHandle);
443 9 : kyc->ctx = ctx;
444 9 : kyc->base_url = GNUNET_strdup (url);
445 9 : kyc->lpt = TALER_EXCHANGE_KLPT_NONE;
446 9 : return kyc;
447 : }
448 :
449 :
450 : enum GNUNET_GenericReturnValue
451 15 : TALER_MERCHANT_get_private_kyc_set_options_ (
452 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
453 : unsigned int num_options,
454 : const struct TALER_MERCHANT_GetPrivateKycOptionValue *options)
455 : {
456 34 : for (unsigned int i = 0; i < num_options; i++)
457 : {
458 34 : const struct TALER_MERCHANT_GetPrivateKycOptionValue *opt =
459 34 : &options[i];
460 :
461 34 : switch (opt->option)
462 : {
463 15 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_END:
464 15 : return GNUNET_OK;
465 2 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_H_WIRE:
466 2 : if (NULL != opt->details.h_wire)
467 : {
468 2 : kyc->h_wire_val = *opt->details.h_wire;
469 2 : kyc->h_wire = &kyc->h_wire_val;
470 2 : kyc->have_h_wire = true;
471 : }
472 2 : break;
473 9 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_EXCHANGE_URL:
474 9 : GNUNET_free (kyc->exchange_url);
475 9 : if (NULL != opt->details.exchange_url)
476 9 : kyc->exchange_url = GNUNET_strdup (opt->details.exchange_url);
477 9 : break;
478 4 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LPT:
479 4 : kyc->lpt = opt->details.lpt;
480 4 : break;
481 4 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_TIMEOUT:
482 4 : kyc->timeout = opt->details.timeout;
483 4 : break;
484 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_INSTANCE_ID:
485 0 : GNUNET_free (kyc->instance_id);
486 0 : if (NULL != opt->details.instance_id)
487 0 : kyc->instance_id = GNUNET_strdup (opt->details.instance_id);
488 0 : break;
489 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_STATUS:
490 0 : GNUNET_free (kyc->lp_status);
491 0 : if (NULL != opt->details.lp_status)
492 0 : kyc->lp_status = GNUNET_strdup (opt->details.lp_status);
493 0 : break;
494 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_STATUS:
495 0 : GNUNET_free (kyc->lp_not_status);
496 0 : if (NULL != opt->details.lp_not_status)
497 0 : kyc->lp_not_status = GNUNET_strdup (opt->details.lp_not_status);
498 0 : break;
499 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_ETAG:
500 0 : if (NULL != opt->details.lp_not_etag)
501 : {
502 0 : kyc->lp_not_etag = *opt->details.lp_not_etag;
503 0 : kyc->have_lp_not_etag = true;
504 : }
505 0 : break;
506 0 : default:
507 0 : GNUNET_break (0);
508 0 : return GNUNET_NO;
509 : }
510 : }
511 0 : return GNUNET_OK;
512 : }
513 :
514 :
515 : enum TALER_ErrorCode
516 9 : TALER_MERCHANT_get_private_kyc_start (
517 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
518 : TALER_MERCHANT_GetPrivateKycCallback cb,
519 : TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls)
520 : {
521 : CURL *eh;
522 : unsigned long long tms;
523 : char timeout_ms[32];
524 : char lpt_str[32];
525 : char *base_path;
526 :
527 9 : kyc->cb = cb;
528 9 : kyc->cb_cls = cb_cls;
529 :
530 : /* Build the base path depending on whether instance_id is set */
531 9 : if (NULL != kyc->instance_id)
532 : {
533 0 : GNUNET_asprintf (&base_path,
534 : "%smanagement/instances/%s/",
535 : kyc->base_url,
536 : kyc->instance_id);
537 : }
538 : else
539 : {
540 9 : GNUNET_asprintf (&base_path,
541 : "%sprivate/",
542 : kyc->base_url);
543 : }
544 :
545 9 : GNUNET_snprintf (lpt_str,
546 : sizeof (lpt_str),
547 : "%d",
548 9 : (int) kyc->lpt);
549 18 : tms = kyc->timeout.rel_value_us
550 9 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
551 9 : GNUNET_snprintf (timeout_ms,
552 : sizeof (timeout_ms),
553 : "%llu",
554 : tms);
555 : {
556 : char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1];
557 :
558 9 : if (kyc->have_lp_not_etag)
559 : {
560 : char *end;
561 :
562 0 : end = GNUNET_STRINGS_data_to_string (
563 0 : &kyc->lp_not_etag,
564 : sizeof (kyc->lp_not_etag),
565 : etag_str,
566 : sizeof (etag_str) - 1);
567 0 : *end = '\0';
568 : }
569 : kyc->url
570 36 : = TALER_url_join (
571 : base_path,
572 : "kyc",
573 : "h_wire",
574 9 : kyc->have_h_wire
575 2 : ? GNUNET_h2s_full (&kyc->h_wire_val.hash)
576 : : NULL,
577 : "exchange_url",
578 : kyc->exchange_url,
579 : "timeout_ms",
580 9 : GNUNET_TIME_relative_is_zero (kyc->timeout)
581 : ? NULL
582 : : timeout_ms,
583 : "lpt",
584 9 : TALER_EXCHANGE_KLPT_NONE == kyc->lpt
585 : ? NULL
586 : : lpt_str,
587 : "lp_status",
588 : kyc->lp_status,
589 : "lp_not_status",
590 : kyc->lp_not_status,
591 : "lp_not_etag",
592 9 : kyc->have_lp_not_etag
593 : ? etag_str
594 : : NULL,
595 : NULL);
596 : }
597 9 : GNUNET_free (base_path);
598 9 : if (NULL == kyc->url)
599 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
600 9 : eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
601 9 : if (NULL == eh)
602 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
603 9 : if (0 != tms)
604 : {
605 4 : GNUNET_break (CURLE_OK ==
606 : curl_easy_setopt (eh,
607 : CURLOPT_TIMEOUT_MS,
608 : (long) (tms + 100L)));
609 : }
610 9 : kyc->job = GNUNET_CURL_job_add (kyc->ctx,
611 : eh,
612 : &handle_get_kyc_finished,
613 : kyc);
614 9 : if (NULL == kyc->job)
615 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
616 9 : return TALER_EC_NONE;
617 : }
618 :
619 :
620 : void
621 9 : TALER_MERCHANT_get_private_kyc_cancel (
622 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc)
623 : {
624 9 : if (NULL != kyc->job)
625 : {
626 0 : GNUNET_CURL_job_cancel (kyc->job);
627 0 : kyc->job = NULL;
628 : }
629 9 : GNUNET_free (kyc->url);
630 9 : GNUNET_free (kyc->exchange_url);
631 9 : GNUNET_free (kyc->instance_id);
632 9 : GNUNET_free (kyc->lp_status);
633 9 : GNUNET_free (kyc->lp_not_status);
634 9 : GNUNET_free (kyc->base_url);
635 9 : GNUNET_free (kyc);
636 9 : }
637 :
638 :
639 : /* end of merchant_api_get-private-kyc-new.c */
|