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 "platform.h" /* UNNECESSARY? */
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" /* UNNECESSARY? */
27 : #include "taler/taler_json_lib.h"
28 : #include "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_string ("tos_required",
123 : &status->tos_required),
124 : NULL),
125 14 : GNUNET_JSON_spec_mark_optional (
126 : GNUNET_JSON_spec_array_const ("limits",
127 : &limits),
128 : NULL),
129 14 : GNUNET_JSON_spec_end ()
130 : };
131 :
132 14 : if (GNUNET_OK !=
133 14 : GNUNET_JSON_parse (j,
134 : spec,
135 : NULL, NULL))
136 : {
137 0 : GNUNET_break_op (0);
138 0 : return GNUNET_SYSERR;
139 : }
140 28 : if ( (NULL != limits) &&
141 14 : (0 != json_array_size (limits)) )
142 13 : {
143 13 : size_t limit_length = json_array_size (limits);
144 13 : struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
145 : size_t i;
146 : json_t *limit;
147 :
148 42 : json_array_foreach (limits, i, limit)
149 : {
150 29 : struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
151 : struct GNUNET_JSON_Specification ispec[] = {
152 29 : GNUNET_JSON_spec_mark_optional (
153 : GNUNET_JSON_spec_bool ("soft_limit",
154 : &al->soft_limit),
155 : NULL),
156 29 : GNUNET_JSON_spec_relative_time ("timeframe",
157 : &al->timeframe),
158 29 : TALER_JSON_spec_kycte ("operation_type",
159 : &al->operation_type),
160 29 : TALER_JSON_spec_amount_any ("threshold",
161 : &al->threshold),
162 29 : GNUNET_JSON_spec_end ()
163 : };
164 :
165 29 : al->soft_limit = false;
166 29 : if (GNUNET_OK !=
167 29 : GNUNET_JSON_parse (limit,
168 : ispec,
169 : NULL, NULL))
170 : {
171 0 : GNUNET_break_op (0);
172 0 : return GNUNET_SYSERR;
173 : }
174 : }
175 13 : status->limits = ala;
176 13 : status->limits_length = limit_length;
177 13 : gkch->cb (gkch->cb_cls,
178 : res);
179 : }
180 : else
181 : {
182 1 : gkch->cb (gkch->cb_cls,
183 : res);
184 : }
185 14 : GNUNET_JSON_parse_free (spec);
186 14 : return GNUNET_OK;
187 : }
188 :
189 :
190 : /**
191 : * Function called when we're done processing the
192 : * HTTP GET /kyc-check/$H_NORMALIZED_PAYTO request.
193 : *
194 : * @param cls the `struct TALER_EXCHANGE_GetKycCheckHandle`
195 : * @param response_code HTTP response code, 0 on error
196 : * @param response parsed JSON result, NULL on error
197 : */
198 : static void
199 14 : handle_kyc_check_finished (void *cls,
200 : long response_code,
201 : const void *response)
202 : {
203 14 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch = cls;
204 14 : const json_t *j = response;
205 14 : struct TALER_EXCHANGE_GetKycCheckResponse ks = {
206 : .hr.reply = j,
207 14 : .hr.http_status = (unsigned int) response_code
208 : };
209 :
210 14 : gkch->job = NULL;
211 14 : switch (response_code)
212 : {
213 0 : case 0:
214 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
215 0 : break;
216 3 : case MHD_HTTP_OK:
217 : {
218 3 : if (GNUNET_OK !=
219 3 : parse_account_status (gkch,
220 : j,
221 : &ks,
222 : &ks.details.ok))
223 : {
224 0 : GNUNET_break_op (0);
225 0 : ks.hr.http_status = 0;
226 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
227 0 : break;
228 : }
229 3 : TALER_EXCHANGE_get_kyc_check_cancel (gkch);
230 14 : return;
231 : }
232 11 : case MHD_HTTP_ACCEPTED:
233 : {
234 11 : if (GNUNET_OK !=
235 11 : parse_account_status (gkch,
236 : j,
237 : &ks,
238 : &ks.details.accepted))
239 : {
240 0 : GNUNET_break_op (0);
241 0 : ks.hr.http_status = 0;
242 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
243 0 : break;
244 : }
245 11 : TALER_EXCHANGE_get_kyc_check_cancel (gkch);
246 11 : return;
247 : }
248 0 : case MHD_HTTP_NO_CONTENT:
249 0 : break;
250 0 : case MHD_HTTP_BAD_REQUEST:
251 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
252 0 : break;
253 0 : case MHD_HTTP_FORBIDDEN:
254 : {
255 : struct GNUNET_JSON_Specification spec[] = {
256 0 : GNUNET_JSON_spec_fixed_auto (
257 : "expected_account_pub",
258 : &ks.details.forbidden.expected_account_pub),
259 0 : TALER_JSON_spec_ec ("code",
260 : &ks.hr.ec),
261 0 : GNUNET_JSON_spec_end ()
262 : };
263 :
264 0 : if (GNUNET_OK !=
265 0 : GNUNET_JSON_parse (j,
266 : spec,
267 : NULL, NULL))
268 : {
269 0 : GNUNET_break_op (0);
270 0 : ks.hr.http_status = 0;
271 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
272 0 : break;
273 : }
274 0 : break;
275 : }
276 0 : case MHD_HTTP_NOT_FOUND:
277 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
278 0 : break;
279 0 : case MHD_HTTP_CONFLICT:
280 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
281 0 : break;
282 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
283 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
284 0 : break;
285 0 : default:
286 : /* unexpected response code */
287 0 : GNUNET_break_op (0);
288 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
289 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
290 : "Unexpected response code %u/%d for exchange kyc_check\n",
291 : (unsigned int) response_code,
292 : (int) ks.hr.ec);
293 0 : break;
294 : }
295 0 : if (NULL != gkch->cb)
296 : {
297 0 : gkch->cb (gkch->cb_cls,
298 : &ks);
299 0 : gkch->cb = NULL;
300 : }
301 0 : TALER_EXCHANGE_get_kyc_check_cancel (gkch);
302 : }
303 :
304 :
305 : struct TALER_EXCHANGE_GetKycCheckHandle *
306 14 : TALER_EXCHANGE_get_kyc_check_create (
307 : struct GNUNET_CURL_Context *ctx,
308 : const char *url,
309 : const struct TALER_NormalizedPaytoHashP *h_payto,
310 : const union TALER_AccountPrivateKeyP *pk)
311 : {
312 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch;
313 :
314 14 : gkch = GNUNET_new (struct TALER_EXCHANGE_GetKycCheckHandle);
315 14 : gkch->ctx = ctx;
316 14 : gkch->base_url = GNUNET_strdup (url);
317 14 : gkch->h_payto = *h_payto;
318 14 : gkch->account_priv = *pk;
319 14 : return gkch;
320 : }
321 :
322 :
323 : enum GNUNET_GenericReturnValue
324 14 : TALER_EXCHANGE_get_kyc_check_set_options_ (
325 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
326 : unsigned int num_options,
327 : const struct TALER_EXCHANGE_GetKycCheckOptionValue *options)
328 : {
329 42 : for (unsigned int i = 0; i < num_options; i++)
330 : {
331 42 : const struct TALER_EXCHANGE_GetKycCheckOptionValue *opt = &options[i];
332 :
333 42 : switch (opt->option)
334 : {
335 14 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_END:
336 14 : return GNUNET_OK;
337 0 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_KNOWN_RULE_GEN:
338 0 : gkch->known_rule_gen = opt->details.known_rule_gen;
339 0 : break;
340 14 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_LPT:
341 14 : gkch->lpt = opt->details.lpt;
342 14 : break;
343 14 : case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_TIMEOUT:
344 14 : gkch->timeout = opt->details.timeout;
345 14 : break;
346 0 : default:
347 0 : GNUNET_break (0);
348 0 : return GNUNET_SYSERR;
349 : }
350 : }
351 0 : return GNUNET_OK;
352 : }
353 :
354 :
355 : enum TALER_ErrorCode
356 14 : TALER_EXCHANGE_get_kyc_check_start (
357 : struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
358 : TALER_EXCHANGE_GetKycCheckCallback cb,
359 : TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls)
360 : {
361 : CURL *eh;
362 : char arg_str[128];
363 : char timeout_ms[32];
364 : char lpt_str[32];
365 : char krg_str[32];
366 14 : struct curl_slist *job_headers = NULL;
367 : unsigned long long tms;
368 :
369 14 : gkch->cb = cb;
370 14 : gkch->cb_cls = cb_cls;
371 : {
372 : char *hps;
373 :
374 14 : hps = GNUNET_STRINGS_data_to_string_alloc (
375 14 : &gkch->h_payto,
376 : sizeof (gkch->h_payto));
377 14 : GNUNET_snprintf (arg_str,
378 : sizeof (arg_str),
379 : "kyc-check/%s",
380 : hps);
381 14 : GNUNET_free (hps);
382 : }
383 28 : tms = gkch->timeout.rel_value_us
384 14 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
385 14 : GNUNET_snprintf (timeout_ms,
386 : sizeof (timeout_ms),
387 : "%llu",
388 : tms);
389 14 : GNUNET_snprintf (krg_str,
390 : sizeof (krg_str),
391 : "%llu",
392 14 : (unsigned long long) gkch->known_rule_gen);
393 14 : GNUNET_snprintf (lpt_str,
394 : sizeof (lpt_str),
395 : "%d",
396 14 : (int) gkch->lpt);
397 : gkch->url
398 42 : = TALER_url_join (
399 14 : gkch->base_url,
400 : arg_str,
401 : "timeout_ms",
402 14 : GNUNET_TIME_relative_is_zero (gkch->timeout)
403 : ? NULL
404 : : timeout_ms,
405 : "min_rule",
406 14 : 0 == gkch->known_rule_gen
407 : ? NULL
408 : : krg_str,
409 : "lpt",
410 14 : TALER_EXCHANGE_KLPT_NONE == gkch->lpt
411 : ? NULL
412 : : lpt_str,
413 : NULL);
414 14 : if (NULL == gkch->url)
415 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
416 14 : eh = TALER_EXCHANGE_curl_easy_get_ (gkch->url);
417 14 : if (NULL == eh)
418 : {
419 0 : GNUNET_break (0);
420 0 : GNUNET_free (gkch->url);
421 0 : gkch->url = NULL;
422 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
423 : }
424 14 : if (0 != tms)
425 : {
426 14 : GNUNET_break (CURLE_OK ==
427 : curl_easy_setopt (eh,
428 : CURLOPT_TIMEOUT_MS,
429 : (long) (tms + 500L)));
430 : }
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 */
|