Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023--2024 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_kyc.c
19 : * @brief Implementation of the GET /kyc request of the merchant's HTTP API
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_service.h"
29 : #include "merchant_api_curl_defaults.h"
30 : #include <taler/taler_json_lib.h>
31 : #include <taler/taler_signatures.h>
32 :
33 :
34 : /**
35 : * Maximum length of the KYC arrays supported.
36 : */
37 : #define MAX_KYC 1024
38 :
39 : /**
40 : * Handle for a GET /kyc operation.
41 : */
42 : struct TALER_MERCHANT_KycGetHandle
43 : {
44 : /**
45 : * The url for this request.
46 : */
47 : char *url;
48 :
49 : /**
50 : * Handle for the request.
51 : */
52 : struct GNUNET_CURL_Job *job;
53 :
54 : /**
55 : * Function to call with the result.
56 : */
57 : TALER_MERCHANT_KycGetCallback cb;
58 :
59 : /**
60 : * Closure for @a cb.
61 : */
62 : void *cb_cls;
63 :
64 : /**
65 : * Reference to the execution context.
66 : */
67 : struct GNUNET_CURL_Context *ctx;
68 :
69 : };
70 :
71 :
72 : /**
73 : * Parse @a kyc response and call the continuation on success.
74 : *
75 : * @param kyc operation handle
76 : * @param[in,out] kr response details
77 : * @param jkyc array from the reply
78 : * @return #GNUNET_OK on success (callback was called)
79 : */
80 : static enum GNUNET_GenericReturnValue
81 5 : parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc,
82 : struct TALER_MERCHANT_KycResponse *kr,
83 : const json_t *jkyc)
84 : {
85 5 : unsigned int num_kycs = (unsigned int) json_array_size (jkyc);
86 5 : unsigned int num_limits = 0;
87 5 : unsigned int num_kycauths = 0;
88 5 : unsigned int pos_limits = 0;
89 5 : unsigned int pos_kycauths = 0;
90 :
91 5 : if ( (json_array_size (jkyc) != (size_t) num_kycs) ||
92 : (num_kycs > MAX_KYC) )
93 : {
94 0 : GNUNET_break (0);
95 0 : return GNUNET_SYSERR;
96 : }
97 :
98 10 : for (unsigned int i = 0; i<num_kycs; i++)
99 : {
100 5 : const json_t *jlimits = NULL;
101 5 : const json_t *jkycauths = NULL;
102 : struct GNUNET_JSON_Specification spec[] = {
103 5 : GNUNET_JSON_spec_mark_optional (
104 : GNUNET_JSON_spec_array_const (
105 : "limits",
106 : &jlimits),
107 : NULL),
108 5 : GNUNET_JSON_spec_mark_optional (
109 : GNUNET_JSON_spec_array_const (
110 : "payto_kycauths",
111 : &jkycauths),
112 : NULL),
113 5 : GNUNET_JSON_spec_end ()
114 : };
115 :
116 5 : if (GNUNET_OK !=
117 5 : GNUNET_JSON_parse (json_array_get (jkyc,
118 : i),
119 : spec,
120 : NULL, NULL))
121 : {
122 0 : GNUNET_break (0);
123 0 : json_dumpf (json_array_get (jkyc,
124 : i),
125 : stderr,
126 : JSON_INDENT (2));
127 0 : return GNUNET_SYSERR;
128 : }
129 5 : num_limits += json_array_size (jlimits);
130 5 : num_kycauths += json_array_size (jkycauths);
131 : }
132 :
133 :
134 5 : {
135 5 : struct TALER_MERCHANT_AccountKycRedirectDetail kycs[
136 5 : GNUNET_NZL (num_kycs)];
137 5 : struct TALER_EXCHANGE_AccountLimit limits[
138 5 : GNUNET_NZL (num_limits)];
139 5 : struct TALER_FullPayto payto_kycauths[
140 5 : GNUNET_NZL (num_kycauths)];
141 :
142 5 : memset (kycs,
143 : 0,
144 : sizeof (kycs));
145 10 : for (unsigned int i = 0; i<num_kycs; i++)
146 : {
147 5 : struct TALER_MERCHANT_AccountKycRedirectDetail *rd
148 : = &kycs[i];
149 5 : const json_t *jlimits = NULL;
150 5 : const json_t *jkycauths = NULL;
151 : uint32_t hs;
152 : struct GNUNET_JSON_Specification spec[] = {
153 5 : TALER_JSON_spec_full_payto_uri (
154 : "payto_uri",
155 : &rd->payto_uri),
156 5 : TALER_JSON_spec_web_url (
157 : "exchange_url",
158 : &rd->exchange_url),
159 5 : GNUNET_JSON_spec_uint32 (
160 : "exchange_http_status",
161 : &hs),
162 5 : GNUNET_JSON_spec_bool (
163 : "no_keys",
164 : &rd->no_keys),
165 5 : GNUNET_JSON_spec_bool (
166 : "auth_conflict",
167 : &rd->auth_conflict),
168 5 : GNUNET_JSON_spec_mark_optional (
169 : TALER_JSON_spec_ec (
170 : "exchange_code",
171 : &rd->exchange_code),
172 : NULL),
173 5 : GNUNET_JSON_spec_mark_optional (
174 5 : GNUNET_JSON_spec_fixed_auto (
175 : "access_token",
176 : &rd->access_token),
177 : &rd->no_access_token),
178 5 : GNUNET_JSON_spec_mark_optional (
179 : GNUNET_JSON_spec_array_const (
180 : "limits",
181 : &jlimits),
182 : NULL),
183 5 : GNUNET_JSON_spec_mark_optional (
184 : GNUNET_JSON_spec_array_const (
185 : "payto_kycauths",
186 : &jkycauths),
187 : NULL),
188 5 : GNUNET_JSON_spec_end ()
189 : };
190 : size_t j;
191 : json_t *jlimit;
192 : json_t *jkycauth;
193 :
194 5 : if (GNUNET_OK !=
195 5 : GNUNET_JSON_parse (json_array_get (jkyc,
196 : i),
197 : spec,
198 : NULL, NULL))
199 : {
200 0 : GNUNET_break (0);
201 0 : json_dumpf (json_array_get (jkyc,
202 : i),
203 : stderr,
204 : JSON_INDENT (2));
205 0 : return GNUNET_SYSERR;
206 : }
207 5 : rd->exchange_http_status = (unsigned int) hs;
208 5 : rd->limits = &limits[pos_limits];
209 5 : rd->limits_length = json_array_size (jlimits);
210 10 : json_array_foreach (jlimits, j, jlimit)
211 : {
212 5 : struct TALER_EXCHANGE_AccountLimit *limit
213 : = &limits[pos_limits];
214 : struct GNUNET_JSON_Specification jspec[] = {
215 5 : TALER_JSON_spec_kycte (
216 : "operation_type",
217 : &limit->operation_type),
218 5 : GNUNET_JSON_spec_relative_time (
219 : "timeframe",
220 : &limit->timeframe),
221 5 : TALER_JSON_spec_amount_any (
222 : "threshold",
223 : &limit->threshold),
224 5 : GNUNET_JSON_spec_mark_optional (
225 : GNUNET_JSON_spec_bool (
226 : "soft_limit",
227 : &limit->soft_limit),
228 : NULL),
229 5 : GNUNET_JSON_spec_end ()
230 : };
231 :
232 5 : GNUNET_assert (pos_limits < num_limits);
233 5 : limit->soft_limit = false;
234 5 : if (GNUNET_OK !=
235 5 : GNUNET_JSON_parse (jlimit,
236 : jspec,
237 : NULL, NULL))
238 : {
239 0 : GNUNET_break (0);
240 0 : json_dumpf (json_array_get (jkyc,
241 : i),
242 : stderr,
243 : JSON_INDENT (2));
244 0 : return GNUNET_SYSERR;
245 : }
246 5 : pos_limits++;
247 : }
248 5 : rd->payto_kycauths = &payto_kycauths[pos_kycauths];
249 5 : rd->pkycauth_length = json_array_size (jkycauths);
250 6 : json_array_foreach (jkycauths, j, jkycauth)
251 : {
252 1 : GNUNET_assert (pos_kycauths < num_kycauths);
253 : payto_kycauths[pos_kycauths].full_payto
254 1 : = (char *) json_string_value (jkycauth);
255 1 : if (NULL == payto_kycauths[pos_kycauths].full_payto)
256 : {
257 0 : GNUNET_break (0);
258 0 : json_dumpf (json_array_get (jkyc,
259 : i),
260 : stderr,
261 : JSON_INDENT (2));
262 0 : return GNUNET_SYSERR;
263 : }
264 1 : pos_kycauths++;
265 : }
266 : }
267 5 : kr->details.ok.kycs = kycs;
268 5 : kr->details.ok.kycs_length = num_kycs;
269 5 : kyc->cb (kyc->cb_cls,
270 : kr);
271 : }
272 5 : return GNUNET_OK;
273 : }
274 :
275 :
276 : /**
277 : * Function called when we're done processing the
278 : * HTTP /kyc request.
279 : *
280 : * @param cls the `struct TALER_MERCHANT_KycGetHandle`
281 : * @param response_code HTTP response code, 0 on error
282 : * @param response response body, NULL if not in JSON
283 : */
284 : static void
285 7 : handle_get_kyc_finished (void *cls,
286 : long response_code,
287 : const void *response)
288 : {
289 7 : struct TALER_MERCHANT_KycGetHandle *kyc = cls;
290 7 : const json_t *json = response;
291 7 : struct TALER_MERCHANT_KycResponse kr = {
292 7 : .hr.http_status = (unsigned int) response_code,
293 : .hr.reply = json
294 : };
295 :
296 7 : kyc->job = NULL;
297 7 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
298 : "Got /kyc response with status code %u\n",
299 : (unsigned int) response_code);
300 7 : switch (response_code)
301 : {
302 5 : case MHD_HTTP_OK:
303 : {
304 : const json_t *jkyc;
305 : struct GNUNET_JSON_Specification spec[] = {
306 5 : GNUNET_JSON_spec_array_const ("kyc_data",
307 : &jkyc),
308 5 : GNUNET_JSON_spec_end ()
309 : };
310 :
311 5 : if (GNUNET_OK !=
312 5 : GNUNET_JSON_parse (json,
313 : spec,
314 : NULL, NULL))
315 : {
316 0 : kr.hr.http_status = 0;
317 0 : kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
318 0 : break;
319 : }
320 5 : if (GNUNET_OK !=
321 5 : parse_kyc (kyc,
322 : &kr,
323 : jkyc))
324 : {
325 0 : kr.hr.http_status = 0;
326 0 : kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
327 0 : break;
328 : }
329 : /* parse_kyc called the continuation already */
330 5 : TALER_MERCHANT_kyc_get_cancel (kyc);
331 5 : return;
332 : }
333 2 : case MHD_HTTP_NO_CONTENT:
334 2 : break;
335 0 : case MHD_HTTP_UNAUTHORIZED:
336 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
337 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
338 : /* Nothing really to verify, merchant says we need to authenticate. */
339 0 : break;
340 0 : case MHD_HTTP_SERVICE_UNAVAILABLE:
341 0 : break;
342 0 : default:
343 : /* unexpected response code */
344 0 : kr.hr.ec = TALER_JSON_get_error_code (json);
345 0 : kr.hr.hint = TALER_JSON_get_error_hint (json);
346 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
347 : "Unexpected response code %u/%d\n",
348 : (unsigned int) response_code,
349 : (int) kr.hr.ec);
350 0 : break;
351 : }
352 2 : kyc->cb (kyc->cb_cls,
353 : &kr);
354 2 : TALER_MERCHANT_kyc_get_cancel (kyc);
355 : }
356 :
357 :
358 : /**
359 : * Issue a GET KYC request to the backend.
360 : * Returns KYC status of bank accounts.
361 : *
362 : * @param ctx execution context
363 : * @param[in] url URL to use for the request, consumed!
364 : * @param h_wire which bank account to query, NULL for all
365 : * @param exchange_url which exchange to query, NULL for all
366 : * @param lpt target for long polling
367 : * @param timeout how long to wait for a reply
368 : * @param cb function to call with the result
369 : * @param cb_cls closure for @a cb
370 : * @return handle for this operation, NULL upon errors
371 : */
372 : static struct TALER_MERCHANT_KycGetHandle *
373 7 : kyc_get (struct GNUNET_CURL_Context *ctx,
374 : char *url,
375 : const struct TALER_MerchantWireHashP *h_wire,
376 : const char *exchange_url,
377 : enum TALER_EXCHANGE_KycLongPollTarget lpt,
378 : struct GNUNET_TIME_Relative timeout,
379 : TALER_MERCHANT_KycGetCallback cb,
380 : void *cb_cls)
381 : {
382 : struct TALER_MERCHANT_KycGetHandle *kyc;
383 : CURL *eh;
384 : char timeout_ms[32];
385 : char lpt_str[32];
386 : unsigned long long tms;
387 :
388 7 : kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle);
389 7 : kyc->ctx = ctx;
390 7 : kyc->cb = cb;
391 7 : kyc->cb_cls = cb_cls;
392 7 : GNUNET_snprintf (lpt_str,
393 : sizeof (lpt_str),
394 : "%d",
395 : (int) lpt);
396 14 : tms = timeout.rel_value_us
397 7 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
398 7 : GNUNET_snprintf (timeout_ms,
399 : sizeof (timeout_ms),
400 : "%llu",
401 : tms);
402 : kyc->url
403 16 : = TALER_url_join (
404 : url,
405 : "kyc",
406 : "h_wire",
407 : NULL == h_wire
408 : ? NULL
409 2 : : GNUNET_h2s_full (&h_wire->hash),
410 : "exchange_url",
411 : NULL == exchange_url
412 : ? NULL
413 : : exchange_url,
414 : "timeout_ms",
415 7 : GNUNET_TIME_relative_is_zero (timeout)
416 : ? NULL
417 : : timeout_ms,
418 : "lpt",
419 : TALER_EXCHANGE_KLPT_NONE == lpt
420 : ? NULL
421 : : lpt_str,
422 : NULL);
423 7 : GNUNET_free (url);
424 7 : if (NULL == kyc->url)
425 : {
426 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
427 : "Could not construct request URL.\n");
428 0 : GNUNET_free (kyc);
429 0 : return NULL;
430 : }
431 7 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
432 : "Requesting URL '%s'\n",
433 : kyc->url);
434 7 : eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
435 7 : if (0 != tms)
436 : {
437 4 : GNUNET_break (CURLE_OK ==
438 : curl_easy_setopt (eh,
439 : CURLOPT_TIMEOUT_MS,
440 : (long) (tms + 100L)));
441 : }
442 : kyc->job
443 7 : = GNUNET_CURL_job_add (ctx,
444 : eh,
445 : &handle_get_kyc_finished,
446 : kyc);
447 7 : return kyc;
448 : }
449 :
450 :
451 : struct TALER_MERCHANT_KycGetHandle *
452 7 : TALER_MERCHANT_kyc_get (
453 : struct GNUNET_CURL_Context *ctx,
454 : const char *backend_url,
455 : const struct TALER_MerchantWireHashP *h_wire,
456 : const char *exchange_url,
457 : enum TALER_EXCHANGE_KycLongPollTarget lpt,
458 : struct GNUNET_TIME_Relative timeout,
459 : TALER_MERCHANT_KycGetCallback cb,
460 : void *cb_cls)
461 : {
462 : char *url;
463 :
464 7 : GNUNET_asprintf (&url,
465 : "%sprivate/",
466 : backend_url);
467 7 : return kyc_get (ctx,
468 : url, /* consumed! */
469 : h_wire,
470 : exchange_url,
471 : lpt,
472 : timeout,
473 : cb,
474 : cb_cls);
475 : }
476 :
477 :
478 : struct TALER_MERCHANT_KycGetHandle *
479 0 : TALER_MERCHANT_management_kyc_get (
480 : struct GNUNET_CURL_Context *ctx,
481 : const char *backend_url,
482 : const char *instance_id,
483 : const struct TALER_MerchantWireHashP *h_wire,
484 : const char *exchange_url,
485 : enum TALER_EXCHANGE_KycLongPollTarget lpt,
486 : struct GNUNET_TIME_Relative timeout,
487 : TALER_MERCHANT_KycGetCallback cb,
488 : void *cb_cls)
489 : {
490 : char *url;
491 :
492 0 : GNUNET_asprintf (&url,
493 : "%smanagement/instances/%s/",
494 : backend_url,
495 : instance_id);
496 0 : return kyc_get (ctx,
497 : url, /* consumed! */
498 : h_wire,
499 : exchange_url,
500 : lpt,
501 : timeout,
502 : cb,
503 : cb_cls);
504 : }
505 :
506 :
507 : void
508 7 : TALER_MERCHANT_kyc_get_cancel (
509 : struct TALER_MERCHANT_KycGetHandle *kyc)
510 : {
511 7 : if (NULL != kyc->job)
512 0 : GNUNET_CURL_job_cancel (kyc->job);
513 7 : GNUNET_free (kyc->url);
514 7 : GNUNET_free (kyc);
515 7 : }
|