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