exchange_api_aml_legitimizations_get.c (14555B)
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 parse_measure_details ( 125 const json_t *md_json, 126 struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails *md) 127 { 128 struct GNUNET_JSON_Specification spec[] = { 129 GNUNET_JSON_spec_fixed_auto ("h_payto", 130 &md->h_payto), 131 GNUNET_JSON_spec_uint64 ("rowid", 132 &md->rowid), 133 GNUNET_JSON_spec_timestamp ("start_time", 134 &md->start_time), 135 GNUNET_JSON_spec_object_const ("measures", 136 &md->measures), 137 GNUNET_JSON_spec_bool ("is_finished", 138 &md->is_finished), 139 GNUNET_JSON_spec_end () 140 }; 141 142 if (GNUNET_OK != 143 GNUNET_JSON_parse (md_json, 144 spec, 145 NULL, 146 NULL)) 147 { 148 GNUNET_break_op (0); 149 return GNUNET_SYSERR; 150 } 151 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 handle_aml_legitimizations_get_ok ( 165 struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh, 166 const json_t *j) 167 { 168 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 GNUNET_JSON_spec_array_const ("measures", 175 &measures_array), 176 GNUNET_JSON_spec_end () 177 }; 178 struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails *measures; 179 180 if (GNUNET_OK != 181 GNUNET_JSON_parse (j, 182 spec, 183 NULL, 184 NULL)) 185 { 186 GNUNET_break_op (0); 187 return GNUNET_SYSERR; 188 } 189 190 result.details.ok.measures_length = json_array_size (measures_array); 191 if (0 == result.details.ok.measures_length) 192 { 193 measures = NULL; 194 } 195 else 196 { 197 measures 198 = GNUNET_new_array ( 199 result.details.ok.measures_length, 200 struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails); 201 } 202 for (size_t i = 0; i < result.details.ok.measures_length; i++) 203 { 204 const json_t *measure_json = json_array_get (measures_array, 205 i); 206 207 if (GNUNET_OK != 208 parse_measure_details (measure_json, 209 &measures[i])) 210 { 211 GNUNET_free (measures); 212 return GNUNET_SYSERR; 213 } 214 } 215 result.details.ok.measures = measures; 216 algh->cb (algh->cb_cls, 217 &result); 218 algh->cb = NULL; 219 GNUNET_free (measures); 220 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 handle_aml_legitimizations_get_finished (void *cls, 234 long response_code, 235 const void *response) 236 { 237 struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh = cls; 238 const json_t *j = response; 239 struct TALER_EXCHANGE_AmlLegitimizationsGetResult result = { 240 .hr.reply = j, 241 .hr.http_status = (unsigned int) response_code 242 }; 243 244 algh->job = NULL; 245 switch (response_code) 246 { 247 case 0: 248 result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 249 break; 250 case MHD_HTTP_OK: 251 if (GNUNET_OK != 252 handle_aml_legitimizations_get_ok (algh, 253 j)) 254 { 255 result.hr.http_status = 0; 256 result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 257 } 258 break; 259 case MHD_HTTP_NO_CONTENT: 260 /* can happen */ 261 break; 262 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 result.hr.ec = TALER_JSON_get_error_code (j); 266 result.hr.hint = TALER_JSON_get_error_hint (j); 267 break; 268 case MHD_HTTP_UNAUTHORIZED: 269 /* Invalid officer credentials */ 270 result.hr.ec = TALER_JSON_get_error_code (j); 271 result.hr.hint = TALER_JSON_get_error_hint (j); 272 break; 273 case MHD_HTTP_FORBIDDEN: 274 /* Officer not authorized for this operation */ 275 result.hr.ec = TALER_JSON_get_error_code (j); 276 result.hr.hint = TALER_JSON_get_error_hint (j); 277 break; 278 case MHD_HTTP_NOT_FOUND: 279 /* Officer not found */ 280 result.hr.ec = TALER_JSON_get_error_code (j); 281 result.hr.hint = TALER_JSON_get_error_hint (j); 282 break; 283 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 result.hr.ec = TALER_JSON_get_error_code (j); 287 result.hr.hint = TALER_JSON_get_error_hint (j); 288 break; 289 default: 290 /* unexpected response code */ 291 GNUNET_break_op (0); 292 result.hr.ec = TALER_JSON_get_error_code (j); 293 result.hr.hint = TALER_JSON_get_error_hint (j); 294 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 break; 300 } 301 if (NULL != algh->cb) 302 { 303 algh->cb (algh->cb_cls, 304 &result); 305 algh->cb = NULL; 306 } 307 TALER_EXCHANGE_aml_legitimizations_get_cancel (algh); 308 } 309 310 311 struct TALER_EXCHANGE_AmlLegitimizationsGetHandle * 312 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 algh = GNUNET_new (struct TALER_EXCHANGE_AmlLegitimizationsGetHandle); 320 algh->ctx = ctx; 321 algh->exchange_base_url = GNUNET_strdup (exchange_base_url); 322 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 323 &algh->officer_pub.eddsa_pub); 324 TALER_officer_aml_query_sign (officer_priv, 325 &algh->officer_sig); 326 algh->options.limit = -20; /* Default to last 20 entries */ 327 algh->options.offset = INT64_MAX; /* Default to maximum row id */ 328 algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */ 329 return algh; 330 } 331 332 333 enum GNUNET_GenericReturnValue 334 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 for (unsigned int i = 0; i < num_options; i++) 340 { 341 switch (options[i].option) 342 { 343 case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_END: 344 return GNUNET_OK; 345 case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_LIMIT: 346 algh->options.limit = options[i].details.limit; 347 break; 348 case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_OFFSET: 349 algh->options.offset = options[i].details.offset; 350 break; 351 case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_H_PAYTO: 352 algh->options.h_payto = options[i].details.h_payto; 353 break; 354 case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_ACTIVE: 355 algh->options.active = options[i].details.active; 356 break; 357 default: 358 GNUNET_break (0); 359 return GNUNET_NO; 360 } 361 } 362 return GNUNET_OK; 363 } 364 365 366 enum TALER_EXCHANGE_AmlLegitimizationsGetStartError 367 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 if (NULL != algh->job) 379 { 380 GNUNET_break (0); 381 return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_AGAIN; 382 } 383 algh->cb = cb; 384 algh->cb_cls = cb_cls; 385 if (algh->options.offset > INT64_MAX) 386 { 387 GNUNET_break (0); 388 algh->options.offset = INT64_MAX; 389 } 390 { 391 char *end; 392 393 end = GNUNET_STRINGS_data_to_string ( 394 &algh->officer_pub, 395 sizeof (algh->officer_pub), 396 officer_pub_str, 397 sizeof (officer_pub_str)); 398 *end = '\0'; 399 } 400 if (NULL != algh->options.h_payto) 401 { 402 char *end; 403 404 end = GNUNET_STRINGS_data_to_string ( 405 algh->options.h_payto, 406 sizeof (struct TALER_NormalizedPaytoHashP), 407 paytoh_str, 408 sizeof (paytoh_str)); 409 *end = '\0'; 410 } 411 /* Build query parameters */ 412 GNUNET_snprintf (offset_str, 413 sizeof (offset_str), 414 "%llu", 415 (unsigned long long) algh->options.offset); 416 GNUNET_snprintf (limit_str, 417 sizeof (limit_str), 418 "%lld", 419 (long long) algh->options.limit); 420 GNUNET_snprintf (arg_str, 421 sizeof (arg_str), 422 "aml/%s/legitimizations", 423 officer_pub_str); 424 algh->url = TALER_url_join (algh->exchange_base_url, 425 arg_str, 426 "limit", 427 limit_str, 428 "offset", 429 ( (algh->options.limit > 0) && 430 (0 == algh->options.offset) ) || 431 ( (algh->options.limit <= 0) && 432 (INT64_MAX <= algh->options.offset) ) 433 ? NULL 434 : offset_str, 435 "h_payto", 436 NULL == algh->options.h_payto 437 ? NULL 438 : paytoh_str, 439 "active", 440 TALER_EXCHANGE_YNA_ALL == algh->options.active 441 ? NULL 442 : TALER_yna_to_string (algh->options.active), 443 NULL); 444 if (NULL == algh->url) 445 { 446 GNUNET_break (0); 447 return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_INTERNAL; 448 } 449 450 { 451 CURL *eh; 452 453 eh = TALER_EXCHANGE_curl_easy_get_ (algh->url); 454 if (NULL == eh) 455 { 456 GNUNET_break (0); 457 GNUNET_free (algh->url); 458 algh->url = NULL; 459 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 end = GNUNET_STRINGS_data_to_string ( 469 &algh->officer_sig, 470 sizeof (algh->officer_sig), 471 sig_str, 472 sizeof (sig_str)); 473 *end = '\0'; 474 GNUNET_asprintf (&hdr, 475 "%s: %s", 476 TALER_AML_OFFICER_SIGNATURE_HEADER, 477 sig_str); 478 algh->job_headers = curl_slist_append (NULL, 479 hdr); 480 GNUNET_free (hdr); 481 } 482 algh->job 483 = GNUNET_CURL_job_add2 ( 484 algh->ctx, 485 eh, 486 algh->job_headers, 487 &handle_aml_legitimizations_get_finished, 488 algh); 489 } 490 return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_OK; 491 } 492 493 494 void 495 TALER_EXCHANGE_aml_legitimizations_get_cancel ( 496 struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh) 497 { 498 if (NULL != algh->job) 499 { 500 GNUNET_CURL_job_cancel (algh->job); 501 algh->job = NULL; 502 } 503 curl_slist_free_all (algh->job_headers); 504 GNUNET_free (algh->exchange_base_url); 505 GNUNET_free (algh->url); 506 GNUNET_free (algh); 507 } 508 509 510 /* end of exchange_api_aml_legitimizations_get.c */