Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2021-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 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
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c
19 : * @brief Implementation of the /kyc-check request
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/platform.h"
23 : #include <microhttpd.h> /* just for HTTP check codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler/taler_exchange_service.h"
27 : #include "taler/taler_json_lib.h"
28 : #include "taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h"
29 : #include "taler/taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * @brief A GET /kyc-check/$H_NORMALIZED_PAYTO handle
35 : */
36 : struct TALER_EXCHANGE_GetKycCheckHandle
37 : {
38 :
39 : /**
40 : * The base URL for this request.
41 : */
42 : char *base_url;
43 :
44 : /**
45 : * The full URL for this request, set during _start.
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_EXCHANGE_GetKycCheckCallback cb;
58 :
59 : /**
60 : * Closure for @e cb.
61 : */
62 : TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls;
63 :
64 : /**
65 : * Reference to the execution context.
66 : */
67 : struct GNUNET_CURL_Context *ctx;
68 :
69 : /**
70 : * Hash of the payto URI we are checking.
71 : */
72 : struct TALER_NormalizedPaytoHashP h_payto;
73 :
74 : /**
75 : * Private key to authorize the request.
76 : */
77 : union TALER_AccountPrivateKeyP account_priv;
78 :
79 : /**
80 : * Long polling target.
81 : */
82 : enum TALER_EXCHANGE_KycLongPollTarget lpt;
83 :
84 : /**
85 : * Latest known rule generation (for long polling).
86 : */
87 : uint64_t known_rule_gen;
88 :
89 : /**
90 : * Long polling timeout.
91 : */
92 : struct GNUNET_TIME_Relative timeout;
93 :
94 : };
95 :
96 :
97 : /**
98 : * Parse an account KYC status from JSON and invoke the callback.
99 : *
100 : * @param[in,out] gkch handle
101 : * @param j JSON to parse
102 : * @param res response to fill
103 : * @param status account status field within @a res to fill
104 : * @return #GNUNET_OK on success
105 : */
106 : static enum GNUNET_GenericReturnValue
107 14 : parse_account_status (
108 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
109 : const json_t *j,
110 : struct TALER_EXCHANGE_GetKycCheckResponse *res,
111 : struct TALER_EXCHANGE_AccountKycStatus *status)
112 : {
113 14 : const json_t *limits = NULL;
114 : struct GNUNET_JSON_Specification spec[] = {
115 14 : GNUNET_JSON_spec_bool ("aml_review",
116 : &status->aml_review),
117 14 : GNUNET_JSON_spec_uint64 ("rule_gen",
118 : &status->rule_gen),
119 14 : GNUNET_JSON_spec_fixed_auto ("access_token",
120 : &status->access_token),
121 14 : GNUNET_JSON_spec_mark_optional (
122 : GNUNET_JSON_spec_array_const ("limits",
123 : &limits),
124 : NULL),
125 14 : GNUNET_JSON_spec_end ()
126 : };
127 :
128 14 : if (GNUNET_OK !=
129 14 : GNUNET_JSON_parse (j,
130 : spec,
131 : NULL, NULL))
132 : {
133 0 : GNUNET_break_op (0);
134 0 : return GNUNET_SYSERR;
135 : }
136 28 : if ( (NULL != limits) &&
137 14 : (0 != json_array_size (limits)) )
138 13 : {
139 13 : size_t limit_length = json_array_size (limits);
140 13 : struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
141 : size_t i;
142 : json_t *limit;
143 :
144 42 : json_array_foreach (limits, i, limit)
145 : {
146 29 : struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
147 : struct GNUNET_JSON_Specification ispec[] = {
148 29 : GNUNET_JSON_spec_mark_optional (
149 : GNUNET_JSON_spec_bool ("soft_limit",
150 : &al->soft_limit),
151 : NULL),
152 29 : GNUNET_JSON_spec_relative_time ("timeframe",
153 : &al->timeframe),
154 29 : TALER_JSON_spec_kycte ("operation_type",
155 : &al->operation_type),
156 29 : TALER_JSON_spec_amount_any ("threshold",
157 : &al->threshold),
158 29 : GNUNET_JSON_spec_end ()
159 : };
160 :
161 29 : al->soft_limit = false;
162 29 : if (GNUNET_OK !=
163 29 : GNUNET_JSON_parse (limit,
164 : ispec,
165 : NULL, NULL))
166 : {
167 0 : GNUNET_break_op (0);
168 0 : return GNUNET_SYSERR;
169 : }
170 : }
171 13 : status->limits = ala;
172 13 : status->limits_length = limit_length;
173 13 : gkch->cb (gkch->cb_cls,
174 : res);
175 : }
176 : else
177 : {
178 1 : gkch->cb (gkch->cb_cls,
179 : res);
180 : }
181 14 : GNUNET_JSON_parse_free (spec);
182 14 : return GNUNET_OK;
183 : }
184 :
185 :
186 : /**
187 : * Function called when we're done processing the
188 : * HTTP GET /kyc-check/$H_NORMALIZED_PAYTO request.
189 : *
190 : * @param cls the `struct TALER_EXCHANGE_GetKycCheckHandle`
191 : * @param response_code HTTP response code, 0 on error
192 : * @param response parsed JSON result, NULL on error
193 : */
194 : static void
195 14 : handle_kyc_check_finished (void *cls,
196 : long response_code,
197 : const void *response)
198 : {
199 14 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch = cls;
200 14 : const json_t *j = response;
201 14 : struct TALER_EXCHANGE_GetKycCheckResponse ks = {
202 : .hr.reply = j,
203 14 : .hr.http_status = (unsigned int) response_code
204 : };
205 :
206 14 : gkch->job = NULL;
207 14 : switch (response_code)
208 : {
209 0 : case 0:
210 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
211 0 : break;
212 3 : case MHD_HTTP_OK:
213 : {
214 3 : if (GNUNET_OK !=
215 3 : parse_account_status (gkch,
216 : j,
217 : &ks,
218 : &ks.details.ok))
219 : {
220 0 : GNUNET_break_op (0);
221 0 : ks.hr.http_status = 0;
222 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
223 0 : break;
224 : }
225 3 : TALER_EXCHANGE_get_kyc_check_cancel (gkch);
226 14 : return;
227 : }
228 11 : case MHD_HTTP_ACCEPTED:
229 : {
230 11 : if (GNUNET_OK !=
231 11 : parse_account_status (gkch,
232 : j,
233 : &ks,
234 : &ks.details.accepted))
235 : {
236 0 : GNUNET_break_op (0);
237 0 : ks.hr.http_status = 0;
238 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
239 0 : break;
240 : }
241 11 : TALER_EXCHANGE_get_kyc_check_cancel (gkch);
242 11 : return;
243 : }
244 0 : case MHD_HTTP_NO_CONTENT:
245 0 : break;
246 0 : case MHD_HTTP_BAD_REQUEST:
247 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
248 0 : break;
249 0 : case MHD_HTTP_FORBIDDEN:
250 : {
251 : struct GNUNET_JSON_Specification spec[] = {
252 0 : GNUNET_JSON_spec_fixed_auto (
253 : "expected_account_pub",
254 : &ks.details.forbidden.expected_account_pub),
255 0 : TALER_JSON_spec_ec ("code",
256 : &ks.hr.ec),
257 0 : GNUNET_JSON_spec_end ()
258 : };
259 :
260 0 : if (GNUNET_OK !=
261 0 : GNUNET_JSON_parse (j,
262 : spec,
263 : NULL, NULL))
264 : {
265 0 : GNUNET_break_op (0);
266 0 : ks.hr.http_status = 0;
267 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
268 0 : break;
269 : }
270 0 : break;
271 : }
272 0 : case MHD_HTTP_NOT_FOUND:
273 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
274 0 : break;
275 0 : case MHD_HTTP_CONFLICT:
276 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
277 0 : break;
278 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
279 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
280 0 : break;
281 0 : default:
282 : /* unexpected response code */
283 0 : GNUNET_break_op (0);
284 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
285 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
286 : "Unexpected response code %u/%d for exchange kyc_check\n",
287 : (unsigned int) response_code,
288 : (int) ks.hr.ec);
289 0 : break;
290 : }
291 0 : if (NULL != gkch->cb)
292 : {
293 0 : gkch->cb (gkch->cb_cls,
294 : &ks);
295 0 : gkch->cb = NULL;
296 : }
297 0 : TALER_EXCHANGE_get_kyc_check_cancel (gkch);
298 : }
299 :
300 :
301 : struct TALER_EXCHANGE_GetKycCheckHandle *
302 14 : TALER_EXCHANGE_get_kyc_check_create (
303 : struct GNUNET_CURL_Context *ctx,
304 : const char *url,
305 : const struct TALER_NormalizedPaytoHashP *h_payto,
306 : const union TALER_AccountPrivateKeyP *pk)
307 : {
308 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch;
309 :
310 14 : gkch = GNUNET_new (struct TALER_EXCHANGE_GetKycCheckHandle);
311 14 : gkch->ctx = ctx;
312 14 : gkch->base_url = GNUNET_strdup (url);
313 14 : gkch->h_payto = *h_payto;
314 14 : gkch->account_priv = *pk;
315 14 : return gkch;
316 : }
317 :
318 :
319 : enum GNUNET_GenericReturnValue
320 14 : TALER_EXCHANGE_get_kyc_check_set_options_ (
321 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
322 : unsigned int num_options,
323 : const struct TALER_EXCHANGE_GetKycCheckOptionValue *options)
324 : {
325 42 : for (unsigned int i = 0; i < num_options; i++)
326 : {
327 42 : const struct TALER_EXCHANGE_GetKycCheckOptionValue *opt = &options[i];
328 :
329 42 : switch (opt->option)
330 : {
331 14 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_END:
332 14 : return GNUNET_OK;
333 0 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_KNOWN_RULE_GEN:
334 0 : gkch->known_rule_gen = opt->details.known_rule_gen;
335 0 : break;
336 14 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_LPT:
337 14 : gkch->lpt = opt->details.lpt;
338 14 : break;
339 14 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_TIMEOUT:
340 14 : gkch->timeout = opt->details.timeout;
341 14 : break;
342 0 : default:
343 0 : GNUNET_break (0);
344 0 : return GNUNET_SYSERR;
345 : }
346 : }
347 0 : return GNUNET_OK;
348 : }
349 :
350 :
351 : enum TALER_ErrorCode
352 14 : TALER_EXCHANGE_get_kyc_check_start (
353 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
354 : TALER_EXCHANGE_GetKycCheckCallback cb,
355 : TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls)
356 : {
357 : CURL *eh;
358 : char arg_str[128];
359 : char timeout_ms[32];
360 : char lpt_str[32];
361 : char krg_str[32];
362 14 : struct curl_slist *job_headers = NULL;
363 : unsigned long long tms;
364 :
365 14 : gkch->cb = cb;
366 14 : gkch->cb_cls = cb_cls;
367 : {
368 : char *hps;
369 :
370 14 : hps = GNUNET_STRINGS_data_to_string_alloc (
371 14 : &gkch->h_payto,
372 : sizeof (gkch->h_payto));
373 14 : GNUNET_snprintf (arg_str,
374 : sizeof (arg_str),
375 : "kyc-check/%s",
376 : hps);
377 14 : GNUNET_free (hps);
378 : }
379 28 : tms = gkch->timeout.rel_value_us
380 14 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
381 14 : GNUNET_snprintf (timeout_ms,
382 : sizeof (timeout_ms),
383 : "%llu",
384 : tms);
385 14 : GNUNET_snprintf (krg_str,
386 : sizeof (krg_str),
387 : "%llu",
388 14 : (unsigned long long) gkch->known_rule_gen);
389 14 : GNUNET_snprintf (lpt_str,
390 : sizeof (lpt_str),
391 : "%d",
392 14 : (int) gkch->lpt);
393 : gkch->url
394 42 : = TALER_url_join (
395 14 : gkch->base_url,
396 : arg_str,
397 : "timeout_ms",
398 14 : GNUNET_TIME_relative_is_zero (gkch->timeout)
399 : ? NULL
400 : : timeout_ms,
401 : "min_rule",
402 14 : 0 == gkch->known_rule_gen
403 : ? NULL
404 : : krg_str,
405 : "lpt",
406 14 : TALER_EXCHANGE_KLPT_NONE == gkch->lpt
407 : ? NULL
408 : : lpt_str,
409 : NULL);
410 14 : if (NULL == gkch->url)
411 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
412 14 : eh = TALER_EXCHANGE_curl_easy_get_ (gkch->url);
413 14 : if (NULL == eh)
414 : {
415 0 : GNUNET_break (0);
416 0 : GNUNET_free (gkch->url);
417 0 : gkch->url = NULL;
418 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
419 : }
420 14 : if (0 != tms)
421 : {
422 14 : GNUNET_break (CURLE_OK ==
423 : curl_easy_setopt (eh,
424 : CURLOPT_TIMEOUT_MS,
425 : (long) (tms + 500L)));
426 : }
427 : job_headers
428 14 : = curl_slist_append (
429 : job_headers,
430 : "Content-Type: application/json");
431 : {
432 : union TALER_AccountPublicKeyP account_pub;
433 : union TALER_AccountSignatureP account_sig;
434 : char *sig_hdr;
435 : char *pub_hdr;
436 : char *hdr;
437 :
438 14 : GNUNET_CRYPTO_eddsa_key_get_public (
439 14 : &gkch->account_priv.reserve_priv.eddsa_priv,
440 : &account_pub.reserve_pub.eddsa_pub);
441 14 : TALER_account_kyc_auth_sign (&gkch->account_priv,
442 : &account_sig);
443 :
444 14 : sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
445 : &account_sig,
446 : sizeof (account_sig));
447 14 : GNUNET_asprintf (&hdr,
448 : "%s: %s",
449 : TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
450 : sig_hdr);
451 14 : GNUNET_free (sig_hdr);
452 14 : job_headers = curl_slist_append (job_headers,
453 : hdr);
454 14 : GNUNET_free (hdr);
455 :
456 14 : pub_hdr = GNUNET_STRINGS_data_to_string_alloc (
457 : &account_pub,
458 : sizeof (account_pub));
459 14 : GNUNET_asprintf (&hdr,
460 : "%s: %s",
461 : TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
462 : pub_hdr);
463 14 : GNUNET_free (pub_hdr);
464 14 : job_headers = curl_slist_append (job_headers,
465 : hdr);
466 14 : GNUNET_free (hdr);
467 14 : if (NULL == job_headers)
468 : {
469 0 : GNUNET_break (0);
470 0 : curl_easy_cleanup (eh);
471 0 : GNUNET_free (gkch->url);
472 0 : gkch->url = NULL;
473 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
474 : }
475 : }
476 : gkch->job
477 14 : = GNUNET_CURL_job_add2 (gkch->ctx,
478 : eh,
479 : job_headers,
480 : &handle_kyc_check_finished,
481 : gkch);
482 14 : curl_slist_free_all (job_headers);
483 14 : if (NULL == gkch->job)
484 : {
485 0 : GNUNET_free (gkch->url);
486 0 : gkch->url = NULL;
487 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
488 : }
489 14 : return TALER_EC_NONE;
490 : }
491 :
492 :
493 : void
494 14 : TALER_EXCHANGE_get_kyc_check_cancel (
495 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch)
496 : {
497 14 : if (NULL != gkch->job)
498 : {
499 0 : GNUNET_CURL_job_cancel (gkch->job);
500 0 : gkch->job = NULL;
501 : }
502 14 : GNUNET_free (gkch->url);
503 14 : GNUNET_free (gkch->base_url);
504 14 : GNUNET_free (gkch);
505 14 : }
506 :
507 :
508 : /* end of exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c */
|