exchange_api_lookup_aml_decisions.c (17747B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023, 2024 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_lookup_aml_decisions.c 19 * @brief Implementation of the /aml/$OFFICER_PUB/decisions request 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <microhttpd.h> /* just for HTTP status codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_curl_lib.h> 26 #include "taler/taler_exchange_service.h" 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_handle.h" 29 #include "taler/taler_signatures.h" 30 #include "exchange_api_curl_defaults.h" 31 32 33 /** 34 * @brief A GET /aml/$OFFICER_PUB/decisions Handle 35 */ 36 struct TALER_EXCHANGE_LookupAmlDecisions 37 { 38 39 /** 40 * The url for this request. 41 */ 42 char *url; 43 44 /** 45 * Handle for the request. 46 */ 47 struct GNUNET_CURL_Job *job; 48 49 /** 50 * Function to call with the result. 51 */ 52 TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb; 53 54 /** 55 * Closure for @e cb. 56 */ 57 void *decisions_cb_cls; 58 59 /** 60 * HTTP headers for the job. 61 */ 62 struct curl_slist *job_headers; 63 64 /** 65 * Array with measure information. 66 */ 67 struct TALER_EXCHANGE_MeasureInformation *mip; 68 69 /** 70 * Array with rule information. 71 */ 72 struct TALER_EXCHANGE_KycRule *rp; 73 74 /** 75 * Array with all the measures (of all the rules!). 76 */ 77 const char **mp; 78 }; 79 80 81 /** 82 * Parse AML limits array. 83 * 84 * @param[in,out] lh handle to use for allocations 85 * @param jlimits JSON array with AML rules 86 * @param[out] ds where to write the result 87 * @return #GNUNET_OK on success 88 */ 89 static enum GNUNET_GenericReturnValue 90 parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh, 91 const json_t *jlimits, 92 struct TALER_EXCHANGE_AmlDecision *ds) 93 { 94 struct TALER_EXCHANGE_LegitimizationRuleSet *limits 95 = &ds->limits; 96 const json_t *jrules; 97 const json_t *jmeasures; 98 size_t mip_len; 99 size_t rule_len; 100 size_t total; 101 struct GNUNET_JSON_Specification spec[] = { 102 GNUNET_JSON_spec_timestamp ("expiration_time", 103 &limits->expiration_time), 104 GNUNET_JSON_spec_mark_optional ( 105 GNUNET_JSON_spec_string ("successor_measure", 106 &limits->successor_measure), 107 NULL), 108 GNUNET_JSON_spec_array_const ("rules", 109 &jrules), 110 GNUNET_JSON_spec_object_const ("custom_measures", 111 &jmeasures), 112 GNUNET_JSON_spec_end () 113 }; 114 115 if (GNUNET_OK != 116 GNUNET_JSON_parse (jlimits, 117 spec, 118 NULL, 119 NULL)) 120 { 121 GNUNET_break_op (0); 122 return GNUNET_SYSERR; 123 } 124 125 mip_len = json_object_size (jmeasures); 126 lh->mip = GNUNET_new_array (mip_len, 127 struct TALER_EXCHANGE_MeasureInformation); 128 limits->measures = lh->mip; 129 limits->measures_length = mip_len; 130 131 { 132 const char *measure_name; 133 const json_t *jmeasure; 134 135 json_object_foreach ((json_t*) jmeasures, 136 measure_name, 137 jmeasure) 138 { 139 struct TALER_EXCHANGE_MeasureInformation *mi 140 = &lh->mip[--mip_len]; 141 struct GNUNET_JSON_Specification ispec[] = { 142 GNUNET_JSON_spec_string ("check_name", 143 &mi->check_name), 144 GNUNET_JSON_spec_mark_optional ( 145 GNUNET_JSON_spec_string ("prog_name", 146 &mi->prog_name), 147 NULL), 148 GNUNET_JSON_spec_mark_optional ( 149 GNUNET_JSON_spec_object_const ("context", 150 &mi->context), 151 NULL), 152 GNUNET_JSON_spec_end () 153 }; 154 155 if (GNUNET_OK != 156 GNUNET_JSON_parse (jmeasure, 157 ispec, 158 NULL, 159 NULL)) 160 { 161 GNUNET_break_op (0); 162 return GNUNET_SYSERR; 163 } 164 mi->measure_name = measure_name; 165 } 166 } 167 168 total = 0; 169 170 { 171 const json_t *rule; 172 size_t idx; 173 174 json_array_foreach ((json_t *) jrules, 175 idx, 176 rule) 177 { 178 total += json_array_size (json_object_get (rule, 179 "measures")); 180 } 181 } 182 183 rule_len = json_array_size (jrules); 184 lh->rp = GNUNET_new_array (rule_len, 185 struct TALER_EXCHANGE_KycRule); 186 lh->mp = GNUNET_new_array (total, 187 const char *); 188 189 { 190 const json_t *rule; 191 size_t idx; 192 193 json_array_foreach ((json_t *) jrules, 194 idx, 195 rule) 196 { 197 const json_t *smeasures; 198 struct TALER_EXCHANGE_KycRule *r 199 = &lh->rp[--rule_len]; 200 struct GNUNET_JSON_Specification ispec[] = { 201 TALER_JSON_spec_kycte ("operation_type", 202 &r->operation_type), 203 TALER_JSON_spec_amount_any ("threshold", 204 &r->threshold), 205 GNUNET_JSON_spec_relative_time ("timeframe", 206 &r->timeframe), 207 GNUNET_JSON_spec_array_const ("measures", 208 &smeasures), 209 GNUNET_JSON_spec_mark_optional ( 210 GNUNET_JSON_spec_bool ("exposed", 211 &r->exposed), 212 NULL), 213 GNUNET_JSON_spec_mark_optional ( 214 GNUNET_JSON_spec_bool ("is_and_combinator", 215 &r->is_and_combinator), 216 NULL), 217 GNUNET_JSON_spec_uint32 ("display_priority", 218 &r->display_priority), 219 GNUNET_JSON_spec_end () 220 }; 221 size_t mlen; 222 223 if (GNUNET_OK != 224 GNUNET_JSON_parse (rule, 225 ispec, 226 NULL, 227 NULL)) 228 { 229 GNUNET_break_op (0); 230 return GNUNET_SYSERR; 231 } 232 233 mlen = json_array_size (smeasures); 234 GNUNET_assert (mlen <= total); 235 total -= mlen; 236 237 { 238 size_t midx; 239 const json_t *smeasure; 240 241 json_array_foreach (smeasures, 242 midx, 243 smeasure) 244 { 245 const char *sval = json_string_value (smeasure); 246 247 if (NULL == sval) 248 { 249 GNUNET_break_op (0); 250 return GNUNET_SYSERR; 251 } 252 lh->mp[total + midx] = sval; 253 if (0 == strcasecmp (sval, 254 "verboten")) 255 r->verboten = true; 256 } 257 } 258 r->measures = &lh->mp[total]; 259 r->measures_length = r->verboten ? 0 : total; 260 } 261 } 262 return GNUNET_OK; 263 } 264 265 266 /** 267 * Parse AML decision summary array. 268 * 269 * @param[in,out] lh handle to use for allocations 270 * @param decisions JSON array with AML decision summaries 271 * @param[out] decision_ar where to write the result 272 * @return #GNUNET_OK on success 273 */ 274 static enum GNUNET_GenericReturnValue 275 parse_aml_decisions ( 276 struct TALER_EXCHANGE_LookupAmlDecisions *lh, 277 const json_t *decisions, 278 struct TALER_EXCHANGE_AmlDecision *decision_ar) 279 { 280 json_t *obj; 281 size_t idx; 282 283 json_array_foreach (decisions, idx, obj) 284 { 285 struct TALER_EXCHANGE_AmlDecision *decision = &decision_ar[idx]; 286 const json_t *jlimits; 287 struct GNUNET_JSON_Specification spec[] = { 288 GNUNET_JSON_spec_fixed_auto ("h_payto", 289 &decision->h_payto), 290 GNUNET_JSON_spec_uint64 ("rowid", 291 &decision->rowid), 292 GNUNET_JSON_spec_mark_optional ( 293 GNUNET_JSON_spec_string ("justification", 294 &decision->justification), 295 NULL), 296 GNUNET_JSON_spec_timestamp ("decision_time", 297 &decision->decision_time), 298 GNUNET_JSON_spec_mark_optional ( 299 GNUNET_JSON_spec_object_const ("properties", 300 &decision->jproperties), 301 NULL), 302 GNUNET_JSON_spec_object_const ("limits", 303 &jlimits), 304 GNUNET_JSON_spec_bool ("to_investigate", 305 &decision->to_investigate), 306 GNUNET_JSON_spec_bool ("is_active", 307 &decision->is_active), 308 GNUNET_JSON_spec_end () 309 }; 310 311 if (GNUNET_OK != 312 GNUNET_JSON_parse (obj, 313 spec, 314 NULL, 315 NULL)) 316 { 317 GNUNET_break_op (0); 318 return GNUNET_SYSERR; 319 } 320 if (GNUNET_OK != 321 parse_limits (lh, 322 jlimits, 323 decision)) 324 { 325 GNUNET_break_op (0); 326 return GNUNET_SYSERR; 327 } 328 } 329 return GNUNET_OK; 330 } 331 332 333 /** 334 * Parse the provided decision data from the "200 OK" response. 335 * 336 * @param[in,out] lh handle (callback may be zero'ed out) 337 * @param json json reply with the data for one coin 338 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 339 */ 340 static enum GNUNET_GenericReturnValue 341 parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh, 342 const json_t *json) 343 { 344 struct TALER_EXCHANGE_AmlDecisionsResponse lr = { 345 .hr.reply = json, 346 .hr.http_status = MHD_HTTP_OK 347 }; 348 const json_t *records; 349 struct GNUNET_JSON_Specification spec[] = { 350 GNUNET_JSON_spec_array_const ("records", 351 &records), 352 GNUNET_JSON_spec_end () 353 }; 354 355 if (GNUNET_OK != 356 GNUNET_JSON_parse (json, 357 spec, 358 NULL, 359 NULL)) 360 { 361 GNUNET_break_op (0); 362 return GNUNET_SYSERR; 363 } 364 lr.details.ok.decisions_length 365 = json_array_size (records); 366 { 367 struct TALER_EXCHANGE_AmlDecision decisions[ 368 GNUNET_NZL (lr.details.ok.decisions_length)]; 369 enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; 370 371 memset (decisions, 372 0, 373 sizeof (decisions)); 374 lr.details.ok.decisions = decisions; 375 ret = parse_aml_decisions (lh, 376 records, 377 decisions); 378 if (GNUNET_OK == ret) 379 { 380 lh->decisions_cb (lh->decisions_cb_cls, 381 &lr); 382 lh->decisions_cb = NULL; 383 } 384 GNUNET_free (lh->mip); 385 GNUNET_free (lh->rp); 386 GNUNET_free (lh->mp); 387 return ret; 388 } 389 } 390 391 392 /** 393 * Function called when we're done processing the 394 * HTTP /aml/$OFFICER_PUB/decisions request. 395 * 396 * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions` 397 * @param response_code HTTP response code, 0 on error 398 * @param response parsed JSON result, NULL on error 399 */ 400 static void 401 handle_lookup_finished (void *cls, 402 long response_code, 403 const void *response) 404 { 405 struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls; 406 const json_t *j = response; 407 struct TALER_EXCHANGE_AmlDecisionsResponse lr = { 408 .hr.reply = j, 409 .hr.http_status = (unsigned int) response_code 410 }; 411 412 lh->job = NULL; 413 switch (response_code) 414 { 415 case 0: 416 lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 417 break; 418 case MHD_HTTP_OK: 419 if (GNUNET_OK != 420 parse_decisions_ok (lh, 421 j)) 422 { 423 GNUNET_break_op (0); 424 lr.hr.http_status = 0; 425 lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 426 break; 427 } 428 GNUNET_assert (NULL == lh->decisions_cb); 429 TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); 430 return; 431 case MHD_HTTP_NO_CONTENT: 432 break; 433 case MHD_HTTP_BAD_REQUEST: 434 json_dumpf (j, 435 stderr, 436 JSON_INDENT (2)); 437 lr.hr.ec = TALER_JSON_get_error_code (j); 438 lr.hr.hint = TALER_JSON_get_error_hint (j); 439 /* This should never happen, either us or the exchange is buggy 440 (or API version conflict); just pass JSON reply to the application */ 441 break; 442 case MHD_HTTP_FORBIDDEN: 443 lr.hr.ec = TALER_JSON_get_error_code (j); 444 lr.hr.hint = TALER_JSON_get_error_hint (j); 445 /* Nothing really to verify, exchange says this coin was not melted; we 446 should pass the JSON reply to the application */ 447 break; 448 case MHD_HTTP_NOT_FOUND: 449 lr.hr.ec = TALER_JSON_get_error_code (j); 450 lr.hr.hint = TALER_JSON_get_error_hint (j); 451 /* Nothing really to verify, exchange says this coin was not melted; we 452 should pass the JSON reply to the application */ 453 break; 454 case MHD_HTTP_INTERNAL_SERVER_ERROR: 455 lr.hr.ec = TALER_JSON_get_error_code (j); 456 lr.hr.hint = TALER_JSON_get_error_hint (j); 457 /* Server had an internal issue; we should retry, but this API 458 leaves this to the application */ 459 break; 460 default: 461 /* unexpected response code */ 462 GNUNET_break_op (0); 463 lr.hr.ec = TALER_JSON_get_error_code (j); 464 lr.hr.hint = TALER_JSON_get_error_hint (j); 465 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 466 "Unexpected response code %u/%d for lookup AML decisions\n", 467 (unsigned int) response_code, 468 (int) lr.hr.ec); 469 break; 470 } 471 if (NULL != lh->decisions_cb) 472 lh->decisions_cb (lh->decisions_cb_cls, 473 &lr); 474 TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); 475 } 476 477 478 struct TALER_EXCHANGE_LookupAmlDecisions * 479 TALER_EXCHANGE_lookup_aml_decisions ( 480 struct GNUNET_CURL_Context *ctx, 481 const char *exchange_url, 482 const struct TALER_NormalizedPaytoHashP *h_payto, 483 enum TALER_EXCHANGE_YesNoAll investigation_only, 484 enum TALER_EXCHANGE_YesNoAll active_only, 485 uint64_t offset, 486 int64_t limit, 487 const struct TALER_AmlOfficerPrivateKeyP *officer_priv, 488 TALER_EXCHANGE_LookupAmlDecisionsCallback cb, 489 void *cb_cls) 490 { 491 struct TALER_EXCHANGE_LookupAmlDecisions *lh; 492 CURL *eh; 493 struct TALER_AmlOfficerPublicKeyP officer_pub; 494 struct TALER_AmlOfficerSignatureP officer_sig; 495 char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 496 + 32]; 497 498 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 499 &officer_pub.eddsa_pub); 500 TALER_officer_aml_query_sign (officer_priv, 501 &officer_sig); 502 { 503 char pub_str[sizeof (officer_pub) * 2]; 504 char *end; 505 506 end = GNUNET_STRINGS_data_to_string ( 507 &officer_pub, 508 sizeof (officer_pub), 509 pub_str, 510 sizeof (pub_str)); 511 *end = '\0'; 512 GNUNET_snprintf (arg_str, 513 sizeof (arg_str), 514 "aml/%s/decisions", 515 pub_str); 516 } 517 lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions); 518 lh->decisions_cb = cb; 519 lh->decisions_cb_cls = cb_cls; 520 { 521 char limit_s[24]; 522 char offset_s[24]; 523 char payto_s[sizeof (*h_payto) * 2]; 524 char *end; 525 526 if (NULL != h_payto) 527 { 528 end = GNUNET_STRINGS_data_to_string ( 529 h_payto, 530 sizeof (*h_payto), 531 payto_s, 532 sizeof (payto_s)); 533 *end = '\0'; 534 } 535 GNUNET_snprintf (limit_s, 536 sizeof (limit_s), 537 "%lld", 538 (long long) limit); 539 GNUNET_snprintf (offset_s, 540 sizeof (offset_s), 541 "%llu", 542 (unsigned long long) offset); 543 lh->url = TALER_url_join ( 544 exchange_url, 545 arg_str, 546 "limit", 547 limit_s, 548 "offset", 549 ( ( (limit < 0) && (UINT64_MAX == offset) ) || 550 ( (limit > 0) && (0 == offset) ) ) 551 ? NULL 552 : offset_s, 553 "h_payto", 554 NULL != h_payto 555 ? payto_s 556 : NULL, 557 "active", 558 TALER_EXCHANGE_YNA_ALL != active_only 559 ? TALER_yna_to_string (active_only) 560 : NULL, 561 "investigation", 562 TALER_EXCHANGE_YNA_ALL != investigation_only 563 ? TALER_yna_to_string (investigation_only) 564 : NULL, 565 NULL); 566 } 567 if (NULL == lh->url) 568 { 569 GNUNET_free (lh); 570 return NULL; 571 } 572 eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); 573 if (NULL == eh) 574 { 575 GNUNET_break (0); 576 GNUNET_free (lh->url); 577 GNUNET_free (lh); 578 return NULL; 579 } 580 { 581 char *hdr; 582 char sig_str[sizeof (officer_sig) * 2]; 583 char *end; 584 585 end = GNUNET_STRINGS_data_to_string ( 586 &officer_sig, 587 sizeof (officer_sig), 588 sig_str, 589 sizeof (sig_str)); 590 *end = '\0'; 591 592 GNUNET_asprintf (&hdr, 593 "%s: %s", 594 TALER_AML_OFFICER_SIGNATURE_HEADER, 595 sig_str); 596 lh->job_headers = curl_slist_append (NULL, 597 hdr); 598 GNUNET_free (hdr); 599 lh->job_headers = curl_slist_append (lh->job_headers, 600 "Content-type: application/json"); 601 lh->job = GNUNET_CURL_job_add2 (ctx, 602 eh, 603 lh->job_headers, 604 &handle_lookup_finished, 605 lh); 606 } 607 return lh; 608 } 609 610 611 void 612 TALER_EXCHANGE_lookup_aml_decisions_cancel ( 613 struct TALER_EXCHANGE_LookupAmlDecisions *lh) 614 { 615 if (NULL != lh->job) 616 { 617 GNUNET_CURL_job_cancel (lh->job); 618 lh->job = NULL; 619 } 620 curl_slist_free_all (lh->job_headers); 621 GNUNET_free (lh->url); 622 GNUNET_free (lh); 623 } 624 625 626 /* end of exchange_api_lookup_aml_decisions.c */