Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2021-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 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_kyc_check.c
19 : * @brief Implementation of the /kyc-check request
20 : * @author Christian Grothoff
21 : */
22 : #include "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_exchange_service.h"
27 : #include "taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * @brief A ``/kyc-check`` handle
35 : */
36 : struct TALER_EXCHANGE_KycCheckHandle
37 : {
38 :
39 : /**
40 : * The url for this request.
41 : */
42 : char *url;
43 :
44 : /**
45 : * Handle for the request.
46 : */
47 : struct GNUNET_CURL_Job *job;
48 :
49 : /**
50 : * Function to call with the result.
51 : */
52 : TALER_EXCHANGE_KycStatusCallback cb;
53 :
54 : /**
55 : * Closure for @e cb.
56 : */
57 : void *cb_cls;
58 :
59 : };
60 :
61 :
62 : static enum GNUNET_GenericReturnValue
63 14 : parse_account_status (
64 : struct TALER_EXCHANGE_KycCheckHandle *kch,
65 : const json_t *j,
66 : struct TALER_EXCHANGE_KycStatus *ks,
67 : struct TALER_EXCHANGE_AccountKycStatus *status)
68 : {
69 14 : const json_t *limits = NULL;
70 : struct GNUNET_JSON_Specification spec[] = {
71 14 : GNUNET_JSON_spec_bool ("aml_review",
72 : &status->aml_review),
73 14 : GNUNET_JSON_spec_uint64 ("rule_gen",
74 : &status->rule_gen),
75 14 : GNUNET_JSON_spec_fixed_auto ("access_token",
76 : &status->access_token),
77 14 : GNUNET_JSON_spec_mark_optional (
78 : GNUNET_JSON_spec_array_const ("limits",
79 : &limits),
80 : NULL),
81 14 : GNUNET_JSON_spec_end ()
82 : };
83 :
84 14 : if (GNUNET_OK !=
85 14 : GNUNET_JSON_parse (j,
86 : spec,
87 : NULL, NULL))
88 : {
89 0 : GNUNET_break_op (0);
90 0 : return GNUNET_SYSERR;
91 : }
92 28 : if ( (NULL != limits) &&
93 14 : (0 != json_array_size (limits)) )
94 13 : {
95 13 : size_t limit_length = json_array_size (limits);
96 13 : struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
97 : size_t i;
98 : json_t *limit;
99 :
100 47 : json_array_foreach (limits, i, limit)
101 : {
102 34 : struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
103 : struct GNUNET_JSON_Specification ispec[] = {
104 34 : GNUNET_JSON_spec_mark_optional (
105 : GNUNET_JSON_spec_bool ("soft_limit",
106 : &al->soft_limit),
107 : NULL),
108 34 : GNUNET_JSON_spec_relative_time ("timeframe",
109 : &al->timeframe),
110 34 : TALER_JSON_spec_kycte ("operation_type",
111 : &al->operation_type),
112 34 : TALER_JSON_spec_amount_any ("threshold",
113 : &al->threshold),
114 34 : GNUNET_JSON_spec_end ()
115 : };
116 :
117 34 : al->soft_limit = false;
118 34 : if (GNUNET_OK !=
119 34 : GNUNET_JSON_parse (limit,
120 : ispec,
121 : NULL, NULL))
122 : {
123 0 : GNUNET_break_op (0);
124 0 : return GNUNET_SYSERR;
125 : }
126 : }
127 13 : status->limits = ala;
128 13 : status->limits_length = limit_length;
129 13 : kch->cb (kch->cb_cls,
130 : ks);
131 : }
132 : else
133 : {
134 1 : kch->cb (kch->cb_cls,
135 : ks);
136 : }
137 14 : GNUNET_JSON_parse_free (spec);
138 14 : return GNUNET_OK;
139 : }
140 :
141 :
142 : /**
143 : * Function called when we're done processing the
144 : * HTTP /kyc-check request.
145 : *
146 : * @param cls the `struct TALER_EXCHANGE_KycCheckHandle`
147 : * @param response_code HTTP response code, 0 on error
148 : * @param response parsed JSON result, NULL on error
149 : */
150 : static void
151 16 : handle_kyc_check_finished (void *cls,
152 : long response_code,
153 : const void *response)
154 : {
155 16 : struct TALER_EXCHANGE_KycCheckHandle *kch = cls;
156 16 : const json_t *j = response;
157 16 : struct TALER_EXCHANGE_KycStatus ks = {
158 : .hr.reply = j,
159 16 : .hr.http_status = (unsigned int) response_code
160 : };
161 :
162 16 : kch->job = NULL;
163 16 : switch (response_code)
164 : {
165 0 : case 0:
166 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
167 0 : break;
168 3 : case MHD_HTTP_OK:
169 : {
170 3 : if (GNUNET_OK !=
171 3 : parse_account_status (kch,
172 : j,
173 : &ks,
174 : &ks.details.ok))
175 : {
176 0 : GNUNET_break_op (0);
177 0 : ks.hr.http_status = 0;
178 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
179 0 : break;
180 : }
181 3 : TALER_EXCHANGE_kyc_check_cancel (kch);
182 14 : return;
183 : }
184 11 : case MHD_HTTP_ACCEPTED:
185 : {
186 11 : if (GNUNET_OK !=
187 11 : parse_account_status (kch,
188 : j,
189 : &ks,
190 : &ks.details.accepted))
191 : {
192 0 : GNUNET_break_op (0);
193 0 : ks.hr.http_status = 0;
194 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
195 0 : break;
196 : }
197 11 : TALER_EXCHANGE_kyc_check_cancel (kch);
198 11 : return;
199 : }
200 2 : case MHD_HTTP_NO_CONTENT:
201 2 : break;
202 0 : case MHD_HTTP_BAD_REQUEST:
203 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
204 : /* This should never happen, either us or the exchange is buggy
205 : (or API version conflict); just pass JSON reply to the application */
206 0 : break;
207 0 : case MHD_HTTP_FORBIDDEN:
208 : {
209 : struct GNUNET_JSON_Specification spec[] = {
210 0 : GNUNET_JSON_spec_fixed_auto (
211 : "expected_account_pub",
212 : &ks.details.forbidden.expected_account_pub),
213 0 : TALER_JSON_spec_ec ("code",
214 : &ks.hr.ec),
215 0 : GNUNET_JSON_spec_end ()
216 : };
217 :
218 0 : if (GNUNET_OK !=
219 0 : GNUNET_JSON_parse (j,
220 : spec,
221 : NULL, NULL))
222 : {
223 0 : GNUNET_break_op (0);
224 0 : ks.hr.http_status = 0;
225 0 : ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
226 0 : break;
227 : }
228 0 : break;
229 : }
230 0 : case MHD_HTTP_NOT_FOUND:
231 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
232 0 : break;
233 0 : case MHD_HTTP_CONFLICT:
234 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
235 0 : break;
236 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
237 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
238 : /* Server had an internal issue; we should retry, but this API
239 : leaves this to the application */
240 0 : break;
241 0 : default:
242 : /* unexpected response code */
243 0 : GNUNET_break_op (0);
244 0 : ks.hr.ec = TALER_JSON_get_error_code (j);
245 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
246 : "Unexpected response code %u/%d for exchange kyc_check\n",
247 : (unsigned int) response_code,
248 : (int) ks.hr.ec);
249 0 : break;
250 : }
251 2 : kch->cb (kch->cb_cls,
252 : &ks);
253 2 : TALER_EXCHANGE_kyc_check_cancel (kch);
254 : }
255 :
256 :
257 : struct TALER_EXCHANGE_KycCheckHandle *
258 16 : TALER_EXCHANGE_kyc_check (
259 : struct GNUNET_CURL_Context *ctx,
260 : const char *url,
261 : const struct TALER_NormalizedPaytoHashP *h_payto,
262 : const union TALER_AccountPrivateKeyP *account_priv,
263 : uint64_t known_rule_gen,
264 : enum TALER_EXCHANGE_KycLongPollTarget lpt,
265 : struct GNUNET_TIME_Relative timeout,
266 : TALER_EXCHANGE_KycStatusCallback cb,
267 : void *cb_cls)
268 : {
269 : struct TALER_EXCHANGE_KycCheckHandle *kch;
270 : CURL *eh;
271 : char arg_str[128];
272 : char timeout_ms[32];
273 : char lpt_str[32];
274 : char krg_str[32];
275 16 : struct curl_slist *job_headers = NULL;
276 : unsigned long long tms;
277 :
278 : {
279 : char *hps;
280 :
281 16 : hps = GNUNET_STRINGS_data_to_string_alloc (
282 : h_payto,
283 : sizeof (*h_payto));
284 16 : GNUNET_snprintf (arg_str,
285 : sizeof (arg_str),
286 : "kyc-check/%s",
287 : hps);
288 16 : GNUNET_free (hps);
289 : }
290 32 : tms = timeout.rel_value_us
291 16 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
292 16 : GNUNET_snprintf (timeout_ms,
293 : sizeof (timeout_ms),
294 : "%llu",
295 : tms);
296 16 : GNUNET_snprintf (krg_str,
297 : sizeof (krg_str),
298 : "%llu",
299 : (unsigned long long) known_rule_gen);
300 16 : GNUNET_snprintf (lpt_str,
301 : sizeof (lpt_str),
302 : "%d",
303 : (int) lpt);
304 16 : kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
305 16 : kch->cb = cb;
306 16 : kch->cb_cls = cb_cls;
307 : kch->url
308 32 : = TALER_url_join (
309 : url,
310 : arg_str,
311 : "timeout_ms",
312 16 : GNUNET_TIME_relative_is_zero (timeout)
313 : ? NULL
314 : : timeout_ms,
315 : "min_rule",
316 : 0 == known_rule_gen
317 : ? NULL
318 : : krg_str,
319 : "lpt",
320 : TALER_EXCHANGE_KLPT_NONE == lpt
321 : ? NULL
322 : : lpt_str,
323 : NULL);
324 16 : if (NULL == kch->url)
325 : {
326 0 : GNUNET_free (kch);
327 0 : return NULL;
328 : }
329 16 : eh = TALER_EXCHANGE_curl_easy_get_ (kch->url);
330 16 : if (NULL == eh)
331 : {
332 0 : GNUNET_break (0);
333 0 : GNUNET_free (kch->url);
334 0 : GNUNET_free (kch);
335 0 : return NULL;
336 : }
337 16 : if (0 != tms)
338 : {
339 14 : GNUNET_break (CURLE_OK ==
340 : curl_easy_setopt (eh,
341 : CURLOPT_TIMEOUT_MS,
342 : (long) (tms + 500L)));
343 : }
344 : job_headers
345 16 : = curl_slist_append (
346 : job_headers,
347 : "Content-Type: application/json");
348 : {
349 : union TALER_AccountSignatureP account_sig;
350 : char *sig_hdr;
351 : char *hdr;
352 :
353 16 : TALER_account_kyc_auth_sign (account_priv,
354 : &account_sig);
355 :
356 16 : sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
357 : &account_sig,
358 : sizeof (account_sig));
359 16 : GNUNET_asprintf (&hdr,
360 : "%s: %s",
361 : TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
362 : sig_hdr);
363 16 : GNUNET_free (sig_hdr);
364 16 : job_headers = curl_slist_append (job_headers,
365 : hdr);
366 16 : GNUNET_free (hdr);
367 16 : if (NULL == job_headers)
368 : {
369 0 : GNUNET_break (0);
370 0 : curl_easy_cleanup (eh);
371 0 : return NULL;
372 : }
373 : }
374 : kch->job
375 16 : = GNUNET_CURL_job_add2 (ctx,
376 : eh,
377 : job_headers,
378 : &handle_kyc_check_finished,
379 : kch);
380 16 : curl_slist_free_all (job_headers);
381 16 : return kch;
382 : }
383 :
384 :
385 : void
386 16 : TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch)
387 : {
388 16 : if (NULL != kch->job)
389 : {
390 0 : GNUNET_CURL_job_cancel (kch->job);
391 0 : kch->job = NULL;
392 : }
393 16 : GNUNET_free (kch->url);
394 16 : GNUNET_free (kch);
395 16 : }
396 :
397 :
398 : /* end of exchange_api_kyc_check.c */
|