Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2025 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-aml-OFFICER_PUB-legitimizations.c
19 : * @brief Implementation of the GET /aml/$OFFICER_PUB/legitimizations requests
20 : * @author Christian Grothoff
21 : */
22 : #include <jansson.h>
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_json_lib.h"
28 : #include "taler/exchange/get-aml-OFFICER_PUB-legitimizations.h"
29 : #include "exchange_api_handle.h"
30 : #include "taler/taler_signatures.h"
31 : #include "exchange_api_curl_defaults.h"
32 :
33 :
34 : /**
35 : * Handle for an operation to GET /aml/$OFFICER_PUB/legitimizations.
36 : */
37 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle
38 : {
39 :
40 : /**
41 : * The exchange base URL for this request.
42 : */
43 : char *exchange_base_url;
44 :
45 : /**
46 : * Our execution context.
47 : */
48 : struct GNUNET_CURL_Context *ctx;
49 :
50 : /**
51 : * Handle for the request.
52 : */
53 : struct GNUNET_CURL_Job *job;
54 :
55 : /**
56 : * Signature of the AML officer.
57 : */
58 : struct TALER_AmlOfficerSignatureP officer_sig;
59 :
60 : /**
61 : * Public key of the AML officer.
62 : */
63 : struct TALER_AmlOfficerPublicKeyP officer_pub;
64 :
65 : /**
66 : * Function to call with the result.
67 : */
68 : TALER_EXCHANGE_GetAmlLegitimizationsCallback cb;
69 :
70 : /**
71 : * Closure for @a cb.
72 : */
73 : TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls;
74 :
75 : /**
76 : * The url for this request.
77 : */
78 : char *url;
79 :
80 : /**
81 : * Request options.
82 : */
83 : struct
84 : {
85 : /**
86 : * Limit on number of results.
87 : */
88 : int64_t limit;
89 :
90 : /**
91 : * Row offset from which to return results.
92 : */
93 : uint64_t offset;
94 :
95 : /**
96 : * Hash of payto URI to filter by, NULL for no filter.
97 : */
98 : const struct TALER_NormalizedPaytoHashP *h_payto;
99 :
100 : /**
101 : * Activity filter.
102 : */
103 : enum TALER_EXCHANGE_YesNoAll active;
104 :
105 : } options;
106 :
107 : };
108 :
109 :
110 : /**
111 : * Parse a single measure details entry from JSON.
112 : *
113 : * @param md_json JSON object to parse
114 : * @param[out] md where to store the result
115 : * @return #GNUNET_OK on success
116 : */
117 : static enum GNUNET_GenericReturnValue
118 1 : parse_measure_details (
119 : const json_t *md_json,
120 : struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *md)
121 : {
122 : struct GNUNET_JSON_Specification spec[] = {
123 1 : GNUNET_JSON_spec_fixed_auto ("h_payto",
124 : &md->h_payto),
125 1 : GNUNET_JSON_spec_uint64 ("rowid",
126 : &md->rowid),
127 1 : GNUNET_JSON_spec_timestamp ("start_time",
128 : &md->start_time),
129 1 : GNUNET_JSON_spec_object_const ("measures",
130 : &md->measures),
131 1 : GNUNET_JSON_spec_bool ("is_finished",
132 : &md->is_finished),
133 1 : GNUNET_JSON_spec_end ()
134 : };
135 :
136 1 : if (GNUNET_OK !=
137 1 : GNUNET_JSON_parse (md_json,
138 : spec,
139 : NULL,
140 : NULL))
141 : {
142 0 : GNUNET_break_op (0);
143 0 : return GNUNET_SYSERR;
144 : }
145 1 : return GNUNET_OK;
146 : }
147 :
148 :
149 : /**
150 : * We received an #MHD_HTTP_OK status code. Handle the JSON
151 : * response.
152 : *
153 : * @param algh handle of the request
154 : * @param j JSON response
155 : * @return #GNUNET_OK on success
156 : */
157 : static enum GNUNET_GenericReturnValue
158 1 : handle_aml_legitimizations_get_ok (
159 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
160 : const json_t *j)
161 : {
162 1 : struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = {
163 : .hr.reply = j,
164 : .hr.http_status = MHD_HTTP_OK
165 : };
166 : const json_t *measures_array;
167 : struct GNUNET_JSON_Specification spec[] = {
168 1 : GNUNET_JSON_spec_array_const ("measures",
169 : &measures_array),
170 1 : GNUNET_JSON_spec_end ()
171 : };
172 : struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *measures;
173 :
174 1 : if (GNUNET_OK !=
175 1 : GNUNET_JSON_parse (j,
176 : spec,
177 : NULL,
178 : NULL))
179 : {
180 0 : GNUNET_break_op (0);
181 0 : return GNUNET_SYSERR;
182 : }
183 :
184 1 : result.details.ok.measures_length = json_array_size (measures_array);
185 1 : if (0 == result.details.ok.measures_length)
186 : {
187 0 : measures = NULL;
188 : }
189 : else
190 : {
191 : measures
192 1 : = GNUNET_new_array (
193 : result.details.ok.measures_length,
194 : struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails);
195 : }
196 2 : for (size_t i = 0; i < result.details.ok.measures_length; i++)
197 : {
198 1 : const json_t *measure_json = json_array_get (measures_array,
199 : i);
200 :
201 1 : if (GNUNET_OK !=
202 1 : parse_measure_details (measure_json,
203 1 : &measures[i]))
204 : {
205 0 : GNUNET_free (measures);
206 0 : return GNUNET_SYSERR;
207 : }
208 : }
209 1 : result.details.ok.measures = measures;
210 1 : algh->cb (algh->cb_cls,
211 : &result);
212 1 : algh->cb = NULL;
213 1 : GNUNET_free (measures);
214 1 : return GNUNET_OK;
215 : }
216 :
217 :
218 : /**
219 : * Function called when we're done processing the
220 : * HTTP /aml/$OFFICER_PUB/legitimizations GET request.
221 : *
222 : * @param cls the `struct TALER_EXCHANGE_GetAmlLegitimizationsHandle`
223 : * @param response_code HTTP response code, 0 on error
224 : * @param response parsed JSON result, NULL on error
225 : */
226 : static void
227 1 : handle_aml_legitimizations_get_finished (void *cls,
228 : long response_code,
229 : const void *response)
230 : {
231 1 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh = cls;
232 1 : const json_t *j = response;
233 1 : struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = {
234 : .hr.reply = j,
235 1 : .hr.http_status = (unsigned int) response_code
236 : };
237 :
238 1 : algh->job = NULL;
239 1 : switch (response_code)
240 : {
241 0 : case 0:
242 0 : result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
243 0 : break;
244 1 : case MHD_HTTP_OK:
245 1 : if (GNUNET_OK !=
246 1 : handle_aml_legitimizations_get_ok (algh,
247 : j))
248 : {
249 0 : result.hr.http_status = 0;
250 0 : result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
251 : }
252 1 : break;
253 0 : case MHD_HTTP_NO_CONTENT:
254 : /* can happen */
255 0 : break;
256 0 : case MHD_HTTP_BAD_REQUEST:
257 : /* This should never happen, either us or the exchange is buggy
258 : (or API version conflict); just pass JSON reply to the application */
259 0 : result.hr.ec = TALER_JSON_get_error_code (j);
260 0 : result.hr.hint = TALER_JSON_get_error_hint (j);
261 0 : break;
262 0 : case MHD_HTTP_UNAUTHORIZED:
263 : /* Invalid officer credentials */
264 0 : result.hr.ec = TALER_JSON_get_error_code (j);
265 0 : result.hr.hint = TALER_JSON_get_error_hint (j);
266 0 : break;
267 0 : case MHD_HTTP_FORBIDDEN:
268 : /* Officer not authorized for this operation */
269 0 : result.hr.ec = TALER_JSON_get_error_code (j);
270 0 : result.hr.hint = TALER_JSON_get_error_hint (j);
271 0 : break;
272 0 : case MHD_HTTP_NOT_FOUND:
273 : /* Officer not found */
274 0 : result.hr.ec = TALER_JSON_get_error_code (j);
275 0 : result.hr.hint = TALER_JSON_get_error_hint (j);
276 0 : break;
277 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
278 : /* Server had an internal issue; we should retry, but this API
279 : leaves this to the application */
280 0 : result.hr.ec = TALER_JSON_get_error_code (j);
281 0 : result.hr.hint = TALER_JSON_get_error_hint (j);
282 0 : break;
283 0 : default:
284 : /* unexpected response code */
285 0 : GNUNET_break_op (0);
286 0 : result.hr.ec = TALER_JSON_get_error_code (j);
287 0 : result.hr.hint = TALER_JSON_get_error_hint (j);
288 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
289 : "Unexpected response code %u/%d for GET %s\n",
290 : (unsigned int) response_code,
291 : (int) result.hr.ec,
292 : algh->url);
293 0 : break;
294 : }
295 1 : if (NULL != algh->cb)
296 : {
297 0 : algh->cb (algh->cb_cls,
298 : &result);
299 0 : algh->cb = NULL;
300 : }
301 1 : TALER_EXCHANGE_get_aml_legitimizations_cancel (algh);
302 1 : }
303 :
304 :
305 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *
306 1 : TALER_EXCHANGE_get_aml_legitimizations_create (
307 : struct GNUNET_CURL_Context *ctx,
308 : const char *exchange_base_url,
309 : const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
310 : {
311 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh;
312 :
313 1 : algh = GNUNET_new (struct TALER_EXCHANGE_GetAmlLegitimizationsHandle);
314 1 : algh->ctx = ctx;
315 1 : algh->exchange_base_url = GNUNET_strdup (exchange_base_url);
316 1 : GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
317 : &algh->officer_pub.eddsa_pub);
318 1 : TALER_officer_aml_query_sign (officer_priv,
319 : &algh->officer_sig);
320 1 : algh->options.limit = -20; /* Default to last 20 entries */
321 1 : algh->options.offset = INT64_MAX; /* Default to maximum row id */
322 1 : algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */
323 1 : return algh;
324 : }
325 :
326 :
327 : enum GNUNET_GenericReturnValue
328 1 : TALER_EXCHANGE_get_aml_legitimizations_set_options_ (
329 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
330 : unsigned int num_options,
331 : const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *options)
332 : {
333 3 : for (unsigned int i = 0; i < num_options; i++)
334 : {
335 3 : const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *opt = &options
336 3 : [i];
337 :
338 3 : switch (opt->option)
339 : {
340 1 : case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_END:
341 1 : return GNUNET_OK;
342 0 : case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_LIMIT:
343 0 : algh->options.limit = opt->details.limit;
344 0 : break;
345 0 : case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_OFFSET:
346 0 : if (opt->details.offset > INT64_MAX)
347 : {
348 0 : GNUNET_break (0);
349 0 : return GNUNET_NO;
350 : }
351 0 : algh->options.offset = opt->details.offset;
352 0 : break;
353 1 : case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_H_PAYTO:
354 1 : algh->options.h_payto = opt->details.h_payto;
355 1 : break;
356 1 : case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_ACTIVE:
357 1 : algh->options.active = opt->details.active;
358 1 : break;
359 0 : default:
360 0 : GNUNET_break (0);
361 0 : return GNUNET_NO;
362 : }
363 : }
364 0 : return GNUNET_OK;
365 : }
366 :
367 :
368 : enum TALER_ErrorCode
369 1 : TALER_EXCHANGE_get_aml_legitimizations_start (
370 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
371 : TALER_EXCHANGE_GetAmlLegitimizationsCallback cb,
372 : TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls)
373 : {
374 : char officer_pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2];
375 : char arg_str[sizeof (officer_pub_str) + 64];
376 : char limit_str[24];
377 : char offset_str[24];
378 : char paytoh_str[sizeof (struct TALER_NormalizedPaytoHashP) * 2];
379 :
380 1 : if (NULL != algh->job)
381 : {
382 0 : GNUNET_break (0);
383 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
384 : }
385 1 : algh->cb = cb;
386 1 : algh->cb_cls = cb_cls;
387 1 : if (algh->options.offset > INT64_MAX)
388 : {
389 0 : GNUNET_break (0);
390 0 : algh->options.offset = INT64_MAX;
391 : }
392 : {
393 : char *end;
394 :
395 1 : end = GNUNET_STRINGS_data_to_string (
396 1 : &algh->officer_pub,
397 : sizeof (algh->officer_pub),
398 : officer_pub_str,
399 : sizeof (officer_pub_str));
400 1 : *end = '\0';
401 : }
402 1 : if (NULL != algh->options.h_payto)
403 : {
404 : char *end;
405 :
406 1 : end = GNUNET_STRINGS_data_to_string (
407 1 : algh->options.h_payto,
408 : sizeof (struct TALER_NormalizedPaytoHashP),
409 : paytoh_str,
410 : sizeof (paytoh_str));
411 1 : *end = '\0';
412 : }
413 : /* Build query parameters */
414 1 : GNUNET_snprintf (offset_str,
415 : sizeof (offset_str),
416 : "%llu",
417 1 : (unsigned long long) algh->options.offset);
418 1 : GNUNET_snprintf (limit_str,
419 : sizeof (limit_str),
420 : "%lld",
421 1 : (long long) algh->options.limit);
422 1 : GNUNET_snprintf (arg_str,
423 : sizeof (arg_str),
424 : "aml/%s/legitimizations",
425 : officer_pub_str);
426 2 : algh->url = TALER_url_join (algh->exchange_base_url,
427 : arg_str,
428 : "limit",
429 : limit_str,
430 : "offset",
431 1 : ( (algh->options.limit > 0) &&
432 0 : (0 == algh->options.offset) ) ||
433 1 : ( (algh->options.limit <= 0) &&
434 1 : (INT64_MAX <= algh->options.offset) )
435 : ? NULL
436 : : offset_str,
437 : "h_payto",
438 1 : NULL == algh->options.h_payto
439 : ? NULL
440 : : paytoh_str,
441 : "active",
442 1 : TALER_EXCHANGE_YNA_ALL == algh->options.active
443 : ? NULL
444 1 : : TALER_yna_to_string (algh->options.active),
445 : NULL);
446 1 : if (NULL == algh->url)
447 : {
448 0 : GNUNET_break (0);
449 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
450 : }
451 :
452 : {
453 : CURL *eh;
454 1 : struct curl_slist *job_headers = NULL;
455 :
456 1 : eh = TALER_EXCHANGE_curl_easy_get_ (algh->url);
457 1 : if (NULL == eh)
458 : {
459 0 : GNUNET_break (0);
460 0 : GNUNET_free (algh->url);
461 0 : algh->url = NULL;
462 0 : return TALER_EC_GENERIC_ALLOCATION_FAILURE;
463 : }
464 :
465 : /* Add authentication header for AML officer */
466 : {
467 : char *hdr;
468 : char sig_str[sizeof (algh->officer_sig) * 2];
469 : char *end;
470 :
471 1 : end = GNUNET_STRINGS_data_to_string (
472 1 : &algh->officer_sig,
473 : sizeof (algh->officer_sig),
474 : sig_str,
475 : sizeof (sig_str));
476 1 : *end = '\0';
477 1 : GNUNET_asprintf (&hdr,
478 : "%s: %s",
479 : TALER_AML_OFFICER_SIGNATURE_HEADER,
480 : sig_str);
481 1 : job_headers = curl_slist_append (NULL,
482 : hdr);
483 1 : GNUNET_free (hdr);
484 : }
485 : algh->job
486 1 : = GNUNET_CURL_job_add2 (
487 : algh->ctx,
488 : eh,
489 : job_headers,
490 : &handle_aml_legitimizations_get_finished,
491 : algh);
492 1 : curl_slist_free_all (job_headers);
493 1 : if (NULL == algh->job)
494 : {
495 0 : GNUNET_free (algh->url);
496 0 : algh->url = NULL;
497 0 : return TALER_EC_GENERIC_ALLOCATION_FAILURE;
498 : }
499 : }
500 1 : return TALER_EC_NONE;
501 : }
502 :
503 :
504 : void
505 1 : TALER_EXCHANGE_get_aml_legitimizations_cancel (
506 : struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh)
507 : {
508 1 : if (NULL != algh->job)
509 : {
510 0 : GNUNET_CURL_job_cancel (algh->job);
511 0 : algh->job = NULL;
512 : }
513 1 : GNUNET_free (algh->exchange_base_url);
514 1 : GNUNET_free (algh->url);
515 1 : GNUNET_free (algh);
516 1 : }
517 :
518 :
519 : /* end of exchange_api_aml_legitimizations_get.c */
|