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