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