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 merchant_api_get-private-kyc-new.c
19 : * @brief Implementation of the GET /private/kyc request
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/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 0 : parse_kyc (struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
141 : struct TALER_MERCHANT_GetPrivateKycResponse *kr,
142 : const json_t *jkyc)
143 : {
144 0 : unsigned int num_kycs = (unsigned int) json_array_size (jkyc);
145 0 : unsigned int num_limits = 0;
146 0 : unsigned int num_kycauths = 0;
147 0 : unsigned int pos_limits = 0;
148 0 : unsigned int pos_kycauths = 0;
149 :
150 0 : if ( (json_array_size (jkyc) != (size_t) num_kycs) ||
151 : (num_kycs > MAX_KYC) )
152 : {
153 0 : GNUNET_break (0);
154 0 : return GNUNET_SYSERR;
155 : }
156 :
157 0 : for (unsigned int i = 0; i<num_kycs; i++)
158 : {
159 0 : const json_t *jlimits = NULL;
160 0 : const json_t *jkycauths = NULL;
161 : struct GNUNET_JSON_Specification spec[] = {
162 0 : GNUNET_JSON_spec_mark_optional (
163 : GNUNET_JSON_spec_array_const (
164 : "limits",
165 : &jlimits),
166 : NULL),
167 0 : GNUNET_JSON_spec_mark_optional (
168 : GNUNET_JSON_spec_array_const (
169 : "payto_kycauths",
170 : &jkycauths),
171 : NULL),
172 0 : GNUNET_JSON_spec_end ()
173 : };
174 :
175 0 : if (GNUNET_OK !=
176 0 : GNUNET_JSON_parse (json_array_get (jkyc,
177 : i),
178 : spec,
179 : NULL, NULL))
180 : {
181 0 : GNUNET_break (0);
182 0 : return GNUNET_SYSERR;
183 : }
184 0 : num_limits += json_array_size (jlimits);
185 0 : num_kycauths += json_array_size (jkycauths);
186 : }
187 :
188 0 : {
189 0 : struct TALER_MERCHANT_GetPrivateKycRedirectDetail kycs[
190 0 : GNUNET_NZL (num_kycs)];
191 0 : struct TALER_EXCHANGE_AccountLimit limits[
192 0 : GNUNET_NZL (num_limits)];
193 0 : struct TALER_FullPayto payto_kycauths[
194 0 : GNUNET_NZL (num_kycauths)];
195 :
196 0 : memset (kycs,
197 : 0,
198 : sizeof (kycs));
199 0 : for (unsigned int i = 0; i<num_kycs; i++)
200 : {
201 0 : struct TALER_MERCHANT_GetPrivateKycRedirectDetail *rd
202 : = &kycs[i];
203 0 : const json_t *jlimits = NULL;
204 0 : const json_t *jkycauths = NULL;
205 : uint32_t hs;
206 : struct GNUNET_JSON_Specification spec[] = {
207 0 : TALER_JSON_spec_full_payto_uri (
208 : "payto_uri",
209 : &rd->payto_uri),
210 0 : TALER_JSON_spec_web_url (
211 : "exchange_url",
212 : &rd->exchange_url),
213 0 : GNUNET_JSON_spec_uint32 (
214 : "exchange_http_status",
215 : &hs),
216 0 : GNUNET_JSON_spec_bool (
217 : "no_keys",
218 : &rd->no_keys),
219 0 : GNUNET_JSON_spec_bool (
220 : "auth_conflict",
221 : &rd->auth_conflict),
222 0 : GNUNET_JSON_spec_mark_optional (
223 : TALER_JSON_spec_ec (
224 : "exchange_code",
225 : &rd->exchange_code),
226 : NULL),
227 0 : GNUNET_JSON_spec_mark_optional (
228 0 : GNUNET_JSON_spec_fixed_auto (
229 : "access_token",
230 : &rd->access_token),
231 : &rd->no_access_token),
232 0 : GNUNET_JSON_spec_fixed_auto (
233 : "h_wire",
234 : &rd->h_wire),
235 0 : GNUNET_JSON_spec_mark_optional (
236 : GNUNET_JSON_spec_string (
237 : "status",
238 : &rd->status),
239 : NULL),
240 : /* Mandatory since **v25** */
241 0 : GNUNET_JSON_spec_mark_optional (
242 : GNUNET_JSON_spec_string (
243 : "exchange_currency",
244 : &rd->exchange_currency),
245 : NULL),
246 0 : GNUNET_JSON_spec_mark_optional (
247 : GNUNET_JSON_spec_array_const (
248 : "limits",
249 : &jlimits),
250 : NULL),
251 0 : GNUNET_JSON_spec_mark_optional (
252 : GNUNET_JSON_spec_array_const (
253 : "payto_kycauths",
254 : &jkycauths),
255 : NULL),
256 0 : GNUNET_JSON_spec_end ()
257 : };
258 : size_t j;
259 : json_t *jlimit;
260 : json_t *jkycauth;
261 :
262 0 : if (GNUNET_OK !=
263 0 : GNUNET_JSON_parse (json_array_get (jkyc,
264 : i),
265 : spec,
266 : NULL, NULL))
267 : {
268 0 : GNUNET_break (0);
269 0 : return GNUNET_SYSERR;
270 : }
271 0 : rd->exchange_http_status = (unsigned int) hs;
272 0 : rd->limits = &limits[pos_limits];
273 0 : rd->limits_length = json_array_size (jlimits);
274 0 : json_array_foreach (jlimits, j, jlimit)
275 : {
276 0 : struct TALER_EXCHANGE_AccountLimit *limit
277 : = &limits[pos_limits];
278 : struct GNUNET_JSON_Specification jspec[] = {
279 0 : TALER_JSON_spec_kycte (
280 : "operation_type",
281 : &limit->operation_type),
282 0 : GNUNET_JSON_spec_relative_time (
283 : "timeframe",
284 : &limit->timeframe),
285 0 : TALER_JSON_spec_amount_any (
286 : "threshold",
287 : &limit->threshold),
288 0 : GNUNET_JSON_spec_mark_optional (
289 : GNUNET_JSON_spec_bool (
290 : "soft_limit",
291 : &limit->soft_limit),
292 : NULL),
293 0 : GNUNET_JSON_spec_end ()
294 : };
295 :
296 0 : GNUNET_assert (pos_limits < num_limits);
297 0 : limit->soft_limit = false;
298 0 : if (GNUNET_OK !=
299 0 : GNUNET_JSON_parse (jlimit,
300 : jspec,
301 : NULL, NULL))
302 : {
303 0 : GNUNET_break (0);
304 0 : return GNUNET_SYSERR;
305 : }
306 0 : pos_limits++;
307 : }
308 0 : rd->payto_kycauths = &payto_kycauths[pos_kycauths];
309 0 : rd->pkycauth_length = json_array_size (jkycauths);
310 0 : json_array_foreach (jkycauths, j, jkycauth)
311 : {
312 0 : GNUNET_assert (pos_kycauths < num_kycauths);
313 : payto_kycauths[pos_kycauths].full_payto
314 0 : = (char *) json_string_value (jkycauth);
315 0 : if (NULL == payto_kycauths[pos_kycauths].full_payto)
316 : {
317 0 : GNUNET_break (0);
318 0 : return GNUNET_SYSERR;
319 : }
320 0 : pos_kycauths++;
321 : }
322 : }
323 0 : kr->details.ok.kycs = kycs;
324 0 : kr->details.ok.kycs_length = num_kycs;
325 0 : kyc->cb (kyc->cb_cls,
326 : kr);
327 0 : kyc->cb = NULL;
328 : }
329 0 : return GNUNET_OK;
330 : }
331 :
332 :
333 : /**
334 : * Function called when we're done processing the
335 : * HTTP GET /private/kyc request.
336 : *
337 : * @param cls the `struct TALER_MERCHANT_GetPrivateKycHandle`
338 : * @param response_code HTTP response code, 0 on error
339 : * @param response response body, NULL if not in JSON
340 : */
341 : static void
342 0 : handle_get_kyc_finished (void *cls,
343 : long response_code,
344 : const void *response)
345 : {
346 0 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc = cls;
347 0 : const json_t *json = response;
348 0 : struct TALER_MERCHANT_GetPrivateKycResponse kr = {
349 0 : .hr.http_status = (unsigned int) response_code,
350 : .hr.reply = json
351 : };
352 :
353 0 : kyc->job = NULL;
354 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
355 : "Got /private/kyc response with status code %u\n",
356 : (unsigned int) response_code);
357 0 : switch (response_code)
358 : {
359 0 : case MHD_HTTP_OK:
360 : {
361 : const json_t *jkyc;
362 : struct GNUNET_JSON_Specification spec[] = {
363 0 : GNUNET_JSON_spec_array_const ("kyc_data",
364 : &jkyc),
365 0 : GNUNET_JSON_spec_end ()
366 : };
367 :
368 0 : if (GNUNET_OK !=
369 0 : GNUNET_JSON_parse (json,
370 : spec,
371 : NULL, NULL))
372 : {
373 0 : kr.hr.http_status = 0;
374 0 : kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
375 0 : break;
376 : }
377 0 : if (GNUNET_OK !=
378 0 : parse_kyc (kyc,
379 : &kr,
380 : jkyc))
381 : {
382 0 : kr.hr.http_status = 0;
383 0 : kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
384 0 : break;
385 : }
386 : /* parse_kyc called the continuation already */
387 0 : TALER_MERCHANT_get_private_kyc_cancel (kyc);
388 0 : return;
389 : }
390 0 : case MHD_HTTP_NO_CONTENT:
391 0 : break;
392 0 : case MHD_HTTP_NOT_MODIFIED:
393 : /* ETag matched; nothing changed. No body expected. */
394 0 : break;
395 0 : case MHD_HTTP_BAD_REQUEST:
396 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
397 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
398 0 : break;
399 0 : case MHD_HTTP_UNAUTHORIZED:
400 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
401 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
402 0 : break;
403 0 : case MHD_HTTP_NOT_FOUND:
404 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
405 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
406 0 : break;
407 0 : case MHD_HTTP_NOT_ACCEPTABLE:
408 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
409 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
410 0 : break;
411 0 : case MHD_HTTP_SERVICE_UNAVAILABLE:
412 0 : break;
413 0 : default:
414 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
415 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
416 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
417 : "Unexpected response code %u/%d\n",
418 : (unsigned int) response_code,
419 : (int) kr.hr.ec);
420 0 : break;
421 : }
422 0 : kyc->cb (kyc->cb_cls,
423 : &kr);
424 0 : TALER_MERCHANT_get_private_kyc_cancel (kyc);
425 : }
426 :
427 :
428 : struct TALER_MERCHANT_GetPrivateKycHandle *
429 0 : TALER_MERCHANT_get_private_kyc_create (
430 : struct GNUNET_CURL_Context *ctx,
431 : const char *url)
432 : {
433 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc;
434 :
435 0 : kyc = GNUNET_new (struct TALER_MERCHANT_GetPrivateKycHandle);
436 0 : kyc->ctx = ctx;
437 0 : kyc->base_url = GNUNET_strdup (url);
438 0 : kyc->lpt = TALER_EXCHANGE_KLPT_NONE;
439 0 : return kyc;
440 : }
441 :
442 :
443 : enum GNUNET_GenericReturnValue
444 0 : TALER_MERCHANT_get_private_kyc_set_options_ (
445 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
446 : unsigned int num_options,
447 : const struct TALER_MERCHANT_GetPrivateKycOptionValue *options)
448 : {
449 0 : for (unsigned int i = 0; i < num_options; i++)
450 : {
451 0 : const struct TALER_MERCHANT_GetPrivateKycOptionValue *opt =
452 0 : &options[i];
453 :
454 0 : switch (opt->option)
455 : {
456 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_END:
457 0 : return GNUNET_OK;
458 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_H_WIRE:
459 0 : if (NULL != opt->details.h_wire)
460 : {
461 0 : kyc->h_wire_val = *opt->details.h_wire;
462 0 : kyc->h_wire = &kyc->h_wire_val;
463 0 : kyc->have_h_wire = true;
464 : }
465 0 : break;
466 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_EXCHANGE_URL:
467 0 : GNUNET_free (kyc->exchange_url);
468 0 : if (NULL != opt->details.exchange_url)
469 0 : kyc->exchange_url = GNUNET_strdup (opt->details.exchange_url);
470 0 : break;
471 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LPT:
472 0 : kyc->lpt = opt->details.lpt;
473 0 : break;
474 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_TIMEOUT:
475 0 : kyc->timeout = opt->details.timeout;
476 0 : break;
477 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_INSTANCE_ID:
478 0 : GNUNET_free (kyc->instance_id);
479 0 : if (NULL != opt->details.instance_id)
480 0 : kyc->instance_id = GNUNET_strdup (opt->details.instance_id);
481 0 : break;
482 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_STATUS:
483 0 : GNUNET_free (kyc->lp_status);
484 0 : if (NULL != opt->details.lp_status)
485 0 : kyc->lp_status = GNUNET_strdup (opt->details.lp_status);
486 0 : break;
487 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_STATUS:
488 0 : GNUNET_free (kyc->lp_not_status);
489 0 : if (NULL != opt->details.lp_not_status)
490 0 : kyc->lp_not_status = GNUNET_strdup (opt->details.lp_not_status);
491 0 : break;
492 0 : case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_ETAG:
493 0 : if (NULL != opt->details.lp_not_etag)
494 : {
495 0 : kyc->lp_not_etag = *opt->details.lp_not_etag;
496 0 : kyc->have_lp_not_etag = true;
497 : }
498 0 : break;
499 0 : default:
500 0 : GNUNET_break (0);
501 0 : return GNUNET_NO;
502 : }
503 : }
504 0 : return GNUNET_OK;
505 : }
506 :
507 :
508 : enum TALER_ErrorCode
509 0 : TALER_MERCHANT_get_private_kyc_start (
510 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
511 : TALER_MERCHANT_GetPrivateKycCallback cb,
512 : TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls)
513 : {
514 : CURL *eh;
515 : unsigned long long tms;
516 : char timeout_ms[32];
517 : char lpt_str[32];
518 : char *base_path;
519 :
520 0 : kyc->cb = cb;
521 0 : kyc->cb_cls = cb_cls;
522 :
523 : /* Build the base path depending on whether instance_id is set */
524 0 : if (NULL != kyc->instance_id)
525 : {
526 0 : GNUNET_asprintf (&base_path,
527 : "%smanagement/instances/%s/",
528 : kyc->base_url,
529 : kyc->instance_id);
530 : }
531 : else
532 : {
533 0 : GNUNET_asprintf (&base_path,
534 : "%sprivate/",
535 : kyc->base_url);
536 : }
537 :
538 0 : GNUNET_snprintf (lpt_str,
539 : sizeof (lpt_str),
540 : "%d",
541 0 : (int) kyc->lpt);
542 0 : tms = kyc->timeout.rel_value_us
543 0 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
544 0 : GNUNET_snprintf (timeout_ms,
545 : sizeof (timeout_ms),
546 : "%llu",
547 : tms);
548 : {
549 : char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1];
550 :
551 0 : if (kyc->have_lp_not_etag)
552 : {
553 : char *end;
554 :
555 0 : end = GNUNET_STRINGS_data_to_string (
556 0 : &kyc->lp_not_etag,
557 : sizeof (kyc->lp_not_etag),
558 : etag_str,
559 : sizeof (etag_str) - 1);
560 0 : *end = '\0';
561 : }
562 : kyc->url
563 0 : = TALER_url_join (
564 : base_path,
565 : "kyc",
566 : "h_wire",
567 0 : kyc->have_h_wire
568 0 : ? GNUNET_h2s_full (&kyc->h_wire_val.hash)
569 : : NULL,
570 : "exchange_url",
571 : kyc->exchange_url,
572 : "timeout_ms",
573 0 : GNUNET_TIME_relative_is_zero (kyc->timeout)
574 : ? NULL
575 : : timeout_ms,
576 : "lpt",
577 0 : TALER_EXCHANGE_KLPT_NONE == kyc->lpt
578 : ? NULL
579 : : lpt_str,
580 : "lp_status",
581 : kyc->lp_status,
582 : "lp_not_status",
583 : kyc->lp_not_status,
584 : "lp_not_etag",
585 0 : kyc->have_lp_not_etag
586 : ? etag_str
587 : : NULL,
588 : NULL);
589 : }
590 0 : GNUNET_free (base_path);
591 0 : if (NULL == kyc->url)
592 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
593 0 : eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
594 0 : if (NULL == eh)
595 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
596 0 : if (0 != tms)
597 : {
598 0 : GNUNET_break (CURLE_OK ==
599 : curl_easy_setopt (eh,
600 : CURLOPT_TIMEOUT_MS,
601 : (long) (tms + 100L)));
602 : }
603 0 : kyc->job = GNUNET_CURL_job_add (kyc->ctx,
604 : eh,
605 : &handle_get_kyc_finished,
606 : kyc);
607 0 : if (NULL == kyc->job)
608 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
609 0 : return TALER_EC_NONE;
610 : }
611 :
612 :
613 : void
614 0 : TALER_MERCHANT_get_private_kyc_cancel (
615 : struct TALER_MERCHANT_GetPrivateKycHandle *kyc)
616 : {
617 0 : if (NULL != kyc->job)
618 : {
619 0 : GNUNET_CURL_job_cancel (kyc->job);
620 0 : kyc->job = NULL;
621 : }
622 0 : GNUNET_free (kyc->url);
623 0 : GNUNET_free (kyc->exchange_url);
624 0 : GNUNET_free (kyc->instance_id);
625 0 : GNUNET_free (kyc->lp_status);
626 0 : GNUNET_free (kyc->lp_not_status);
627 0 : GNUNET_free (kyc->base_url);
628 0 : GNUNET_free (kyc);
629 0 : }
630 :
631 :
632 : /* end of merchant_api_get-private-kyc-new.c */
|