exchange_api_get-aml-OFFICER_PUB-legitimizations.c (14784B)
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 parse_measure_details ( 119 const json_t *md_json, 120 struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *md) 121 { 122 struct GNUNET_JSON_Specification spec[] = { 123 GNUNET_JSON_spec_fixed_auto ("h_payto", 124 &md->h_payto), 125 GNUNET_JSON_spec_uint64 ("rowid", 126 &md->rowid), 127 GNUNET_JSON_spec_timestamp ("start_time", 128 &md->start_time), 129 GNUNET_JSON_spec_object_const ("measures", 130 &md->measures), 131 GNUNET_JSON_spec_bool ("is_finished", 132 &md->is_finished), 133 GNUNET_JSON_spec_end () 134 }; 135 136 if (GNUNET_OK != 137 GNUNET_JSON_parse (md_json, 138 spec, 139 NULL, 140 NULL)) 141 { 142 GNUNET_break_op (0); 143 return GNUNET_SYSERR; 144 } 145 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 handle_aml_legitimizations_get_ok ( 159 struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, 160 const json_t *j) 161 { 162 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 GNUNET_JSON_spec_array_const ("measures", 169 &measures_array), 170 GNUNET_JSON_spec_end () 171 }; 172 struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *measures; 173 174 if (GNUNET_OK != 175 GNUNET_JSON_parse (j, 176 spec, 177 NULL, 178 NULL)) 179 { 180 GNUNET_break_op (0); 181 return GNUNET_SYSERR; 182 } 183 184 result.details.ok.measures_length = json_array_size (measures_array); 185 if (0 == result.details.ok.measures_length) 186 { 187 measures = NULL; 188 } 189 else 190 { 191 measures 192 = GNUNET_new_array ( 193 result.details.ok.measures_length, 194 struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails); 195 } 196 for (size_t i = 0; i < result.details.ok.measures_length; i++) 197 { 198 const json_t *measure_json = json_array_get (measures_array, 199 i); 200 201 if (GNUNET_OK != 202 parse_measure_details (measure_json, 203 &measures[i])) 204 { 205 GNUNET_free (measures); 206 return GNUNET_SYSERR; 207 } 208 } 209 result.details.ok.measures = measures; 210 algh->cb (algh->cb_cls, 211 &result); 212 algh->cb = NULL; 213 GNUNET_free (measures); 214 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 handle_aml_legitimizations_get_finished (void *cls, 228 long response_code, 229 const void *response) 230 { 231 struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh = cls; 232 const json_t *j = response; 233 struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = { 234 .hr.reply = j, 235 .hr.http_status = (unsigned int) response_code 236 }; 237 238 algh->job = NULL; 239 switch (response_code) 240 { 241 case 0: 242 result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 243 break; 244 case MHD_HTTP_OK: 245 if (GNUNET_OK != 246 handle_aml_legitimizations_get_ok (algh, 247 j)) 248 { 249 result.hr.http_status = 0; 250 result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 251 } 252 break; 253 case MHD_HTTP_NO_CONTENT: 254 /* can happen */ 255 break; 256 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 result.hr.ec = TALER_JSON_get_error_code (j); 260 result.hr.hint = TALER_JSON_get_error_hint (j); 261 break; 262 case MHD_HTTP_UNAUTHORIZED: 263 /* Invalid officer credentials */ 264 result.hr.ec = TALER_JSON_get_error_code (j); 265 result.hr.hint = TALER_JSON_get_error_hint (j); 266 break; 267 case MHD_HTTP_FORBIDDEN: 268 /* Officer not authorized for this operation */ 269 result.hr.ec = TALER_JSON_get_error_code (j); 270 result.hr.hint = TALER_JSON_get_error_hint (j); 271 break; 272 case MHD_HTTP_NOT_FOUND: 273 /* Officer not found */ 274 result.hr.ec = TALER_JSON_get_error_code (j); 275 result.hr.hint = TALER_JSON_get_error_hint (j); 276 break; 277 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 result.hr.ec = TALER_JSON_get_error_code (j); 281 result.hr.hint = TALER_JSON_get_error_hint (j); 282 break; 283 default: 284 /* unexpected response code */ 285 GNUNET_break_op (0); 286 result.hr.ec = TALER_JSON_get_error_code (j); 287 result.hr.hint = TALER_JSON_get_error_hint (j); 288 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 break; 294 } 295 if (NULL != algh->cb) 296 { 297 algh->cb (algh->cb_cls, 298 &result); 299 algh->cb = NULL; 300 } 301 TALER_EXCHANGE_get_aml_legitimizations_cancel (algh); 302 } 303 304 305 struct TALER_EXCHANGE_GetAmlLegitimizationsHandle * 306 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 algh = GNUNET_new (struct TALER_EXCHANGE_GetAmlLegitimizationsHandle); 314 algh->ctx = ctx; 315 algh->exchange_base_url = GNUNET_strdup (exchange_base_url); 316 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 317 &algh->officer_pub.eddsa_pub); 318 TALER_officer_aml_query_sign (officer_priv, 319 &algh->officer_sig); 320 algh->options.limit = -20; /* Default to last 20 entries */ 321 algh->options.offset = INT64_MAX; /* Default to maximum row id */ 322 algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */ 323 return algh; 324 } 325 326 327 enum GNUNET_GenericReturnValue 328 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 for (unsigned int i = 0; i < num_options; i++) 334 { 335 const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *opt = &options 336 [i]; 337 338 switch (opt->option) 339 { 340 case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_END: 341 return GNUNET_OK; 342 case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_LIMIT: 343 algh->options.limit = opt->details.limit; 344 break; 345 case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_OFFSET: 346 if (opt->details.offset > INT64_MAX) 347 { 348 GNUNET_break (0); 349 return GNUNET_NO; 350 } 351 algh->options.offset = opt->details.offset; 352 break; 353 case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_H_PAYTO: 354 algh->options.h_payto = opt->details.h_payto; 355 break; 356 case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_ACTIVE: 357 algh->options.active = opt->details.active; 358 break; 359 default: 360 GNUNET_break (0); 361 return GNUNET_NO; 362 } 363 } 364 return GNUNET_OK; 365 } 366 367 368 enum TALER_ErrorCode 369 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 if (NULL != algh->job) 381 { 382 GNUNET_break (0); 383 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 384 } 385 algh->cb = cb; 386 algh->cb_cls = cb_cls; 387 if (algh->options.offset > INT64_MAX) 388 { 389 GNUNET_break (0); 390 algh->options.offset = INT64_MAX; 391 } 392 { 393 char *end; 394 395 end = GNUNET_STRINGS_data_to_string ( 396 &algh->officer_pub, 397 sizeof (algh->officer_pub), 398 officer_pub_str, 399 sizeof (officer_pub_str)); 400 *end = '\0'; 401 } 402 if (NULL != algh->options.h_payto) 403 { 404 char *end; 405 406 end = GNUNET_STRINGS_data_to_string ( 407 algh->options.h_payto, 408 sizeof (struct TALER_NormalizedPaytoHashP), 409 paytoh_str, 410 sizeof (paytoh_str)); 411 *end = '\0'; 412 } 413 /* Build query parameters */ 414 GNUNET_snprintf (offset_str, 415 sizeof (offset_str), 416 "%llu", 417 (unsigned long long) algh->options.offset); 418 GNUNET_snprintf (limit_str, 419 sizeof (limit_str), 420 "%lld", 421 (long long) algh->options.limit); 422 GNUNET_snprintf (arg_str, 423 sizeof (arg_str), 424 "aml/%s/legitimizations", 425 officer_pub_str); 426 algh->url = TALER_url_join (algh->exchange_base_url, 427 arg_str, 428 "limit", 429 limit_str, 430 "offset", 431 ( (algh->options.limit > 0) && 432 (0 == algh->options.offset) ) || 433 ( (algh->options.limit <= 0) && 434 (INT64_MAX <= algh->options.offset) ) 435 ? NULL 436 : offset_str, 437 "h_payto", 438 NULL == algh->options.h_payto 439 ? NULL 440 : paytoh_str, 441 "active", 442 TALER_EXCHANGE_YNA_ALL == algh->options.active 443 ? NULL 444 : TALER_yna_to_string (algh->options.active), 445 NULL); 446 if (NULL == algh->url) 447 { 448 GNUNET_break (0); 449 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 450 } 451 452 { 453 CURL *eh; 454 struct curl_slist *job_headers = NULL; 455 456 eh = TALER_EXCHANGE_curl_easy_get_ (algh->url); 457 if (NULL == eh) 458 { 459 GNUNET_break (0); 460 GNUNET_free (algh->url); 461 algh->url = NULL; 462 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 end = GNUNET_STRINGS_data_to_string ( 472 &algh->officer_sig, 473 sizeof (algh->officer_sig), 474 sig_str, 475 sizeof (sig_str)); 476 *end = '\0'; 477 GNUNET_asprintf (&hdr, 478 "%s: %s", 479 TALER_AML_OFFICER_SIGNATURE_HEADER, 480 sig_str); 481 job_headers = curl_slist_append (NULL, 482 hdr); 483 GNUNET_free (hdr); 484 } 485 algh->job 486 = GNUNET_CURL_job_add2 ( 487 algh->ctx, 488 eh, 489 job_headers, 490 &handle_aml_legitimizations_get_finished, 491 algh); 492 curl_slist_free_all (job_headers); 493 if (NULL == algh->job) 494 { 495 GNUNET_free (algh->url); 496 algh->url = NULL; 497 return TALER_EC_GENERIC_ALLOCATION_FAILURE; 498 } 499 } 500 return TALER_EC_NONE; 501 } 502 503 504 void 505 TALER_EXCHANGE_get_aml_legitimizations_cancel ( 506 struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh) 507 { 508 if (NULL != algh->job) 509 { 510 GNUNET_CURL_job_cancel (algh->job); 511 algh->job = NULL; 512 } 513 GNUNET_free (algh->exchange_base_url); 514 GNUNET_free (algh->url); 515 GNUNET_free (algh); 516 } 517 518 519 /* end of exchange_api_aml_legitimizations_get.c */