exchange_api_post-aml-OFFICER_PUB-decision.c (17356B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023, 2024, 2026 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_post-aml-OFFICER_PUB-decision.c 19 * @brief functions to add an AML decision by an AML officer 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include "taler/taler_json_lib.h" 24 #include <microhttpd.h> 25 #include <gnunet/gnunet_curl_lib.h> 26 #include "taler/taler_exchange_service.h" 27 #include "taler/taler-exchange/post-aml-OFFICER_PUB-decision.h" 28 #include "exchange_api_curl_defaults.h" 29 #include "taler/taler_signatures.h" 30 #include "taler/taler_curl_lib.h" 31 32 33 /** 34 * @brief A POST /aml/$OFFICER_PUB/decision Handle 35 */ 36 struct TALER_EXCHANGE_PostAmlDecisionHandle 37 { 38 39 /** 40 * The base URL of the exchange. 41 */ 42 char *base_url; 43 44 /** 45 * The full URL for this request. 46 */ 47 char *url; 48 49 /** 50 * Minor context that holds body and headers. 51 */ 52 struct TALER_CURL_PostContext post_ctx; 53 54 /** 55 * Handle for the request. 56 */ 57 struct GNUNET_CURL_Job *job; 58 59 /** 60 * Function to call with the result. 61 */ 62 TALER_EXCHANGE_PostAmlDecisionCallback cb; 63 64 /** 65 * Closure for @e cb. 66 */ 67 TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls; 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 74 /** 75 * Public key of the AML officer. 76 */ 77 struct TALER_AmlOfficerPublicKeyP officer_pub; 78 79 /** 80 * Private key of the AML officer. 81 */ 82 struct TALER_AmlOfficerPrivateKeyP officer_priv; 83 84 /** 85 * Hash of the payto URI of the account the decision is about. 86 */ 87 struct TALER_NormalizedPaytoHashP h_payto; 88 89 /** 90 * When was the decision made. 91 */ 92 struct GNUNET_TIME_Timestamp decision_time; 93 94 /** 95 * Human-readable justification. 96 */ 97 char *justification; 98 99 /** 100 * True to keep the investigation open. 101 */ 102 bool keep_investigating; 103 104 /** 105 * Pre-built new_rules JSON object. 106 */ 107 json_t *new_rules; 108 109 /** 110 * Optional: full payto URI string, may be NULL. 111 */ 112 char *payto_uri_str; 113 114 /** 115 * Optional: space-separated list of measures to trigger immediately 116 * (from options, may be NULL). 117 */ 118 char *new_measures; 119 120 /** 121 * Optional: JSON object with account properties 122 * (from options, may be NULL). 123 */ 124 json_t *properties; 125 126 /** 127 * Optional: JSON array of events to trigger 128 * (from options; may be NULL). 129 */ 130 json_t *jevents; 131 132 /** 133 * Optional: JSON object with KYC attributes 134 * (from options; may be NULL). 135 */ 136 json_t *attributes; 137 138 /** 139 * Optional: expiration time for KYC attributes. 140 * Only meaningful if @e attributes is non-NULL. 141 */ 142 struct GNUNET_TIME_Timestamp attributes_expiration; 143 144 }; 145 146 147 /** 148 * Function called when we're done processing the 149 * HTTP POST /aml/$OFFICER_PUB/decision request. 150 * 151 * @param cls the `struct TALER_EXCHANGE_PostAmlDecisionHandle *` 152 * @param response_code HTTP response code, 0 on error 153 * @param response response body, NULL if not in JSON 154 */ 155 static void 156 handle_post_aml_decision_finished (void *cls, 157 long response_code, 158 const void *response) 159 { 160 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh = cls; 161 const json_t *json = response; 162 struct TALER_EXCHANGE_PostAmlDecisionResponse pr = { 163 .hr.http_status = (unsigned int) response_code, 164 .hr.reply = json 165 }; 166 167 padh->job = NULL; 168 switch (response_code) 169 { 170 case 0: 171 pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 172 pr.hr.hint = "server offline?"; 173 break; 174 case MHD_HTTP_NO_CONTENT: 175 break; 176 case MHD_HTTP_FORBIDDEN: 177 pr.hr.ec = TALER_JSON_get_error_code (json); 178 pr.hr.hint = TALER_JSON_get_error_hint (json); 179 break; 180 case MHD_HTTP_CONFLICT: 181 pr.hr.ec = TALER_JSON_get_error_code (json); 182 pr.hr.hint = TALER_JSON_get_error_hint (json); 183 break; 184 default: 185 GNUNET_break_op (0); 186 pr.hr.ec = TALER_JSON_get_error_code (json); 187 pr.hr.hint = TALER_JSON_get_error_hint (json); 188 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 189 "Unexpected response code %u/%d for POST AML decision\n", 190 (unsigned int) response_code, 191 (int) pr.hr.ec); 192 break; 193 } 194 if (NULL != padh->cb) 195 { 196 padh->cb (padh->cb_cls, 197 &pr); 198 padh->cb = NULL; 199 } 200 TALER_EXCHANGE_post_aml_decision_cancel (padh); 201 } 202 203 204 /** 205 * Build the new_rules JSON object from rules and measures arrays. 206 * 207 * @param successor_measure optional successor measure name 208 * @param expiration_time when the new rules expire 209 * @param num_rules length of @a rules 210 * @param rules the rules array 211 * @param num_measures length of @a measures 212 * @param measures the measures array 213 * @return new JSON object (caller owns reference), NULL on error 214 */ 215 static json_t * 216 build_new_rules ( 217 const char *successor_measure, 218 struct GNUNET_TIME_Timestamp expiration_time, 219 unsigned int num_rules, 220 const struct TALER_EXCHANGE_AccountRule rules[static num_rules], 221 unsigned int num_measures, 222 const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures]) 223 { 224 json_t *jrules; 225 json_t *jmeasures; 226 227 jrules = json_array (); 228 GNUNET_assert (NULL != jrules); 229 for (unsigned int i = 0; i < num_rules; i++) 230 { 231 const struct TALER_EXCHANGE_AccountRule *al = &rules[i]; 232 json_t *ameasures; 233 json_t *rule; 234 235 ameasures = json_array (); 236 GNUNET_assert (NULL != ameasures); 237 for (unsigned int j = 0; j < al->num_measures; j++) 238 GNUNET_assert (0 == 239 json_array_append_new (ameasures, 240 json_string (al->measures[j]))); 241 rule = GNUNET_JSON_PACK ( 242 TALER_JSON_pack_kycte ("operation_type", 243 al->operation_type), 244 TALER_JSON_pack_amount ("threshold", 245 &al->threshold), 246 GNUNET_JSON_pack_time_rel ("timeframe", 247 al->timeframe), 248 GNUNET_JSON_pack_array_steal ("measures", 249 ameasures), 250 GNUNET_JSON_pack_allow_null ( 251 GNUNET_JSON_pack_string ("rule_name", 252 al->rule_name)), 253 GNUNET_JSON_pack_bool ("exposed", 254 al->exposed), 255 GNUNET_JSON_pack_bool ("is_and_combinator", 256 al->is_and_combinator), 257 GNUNET_JSON_pack_uint64 ("display_priority", 258 al->display_priority) 259 ); 260 GNUNET_break (0 == 261 json_array_append_new (jrules, 262 rule)); 263 } 264 265 jmeasures = json_object (); 266 GNUNET_assert (NULL != jmeasures); 267 for (unsigned int i = 0; i < num_measures; i++) 268 { 269 const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i]; 270 json_t *measure; 271 272 measure = GNUNET_JSON_PACK ( 273 GNUNET_JSON_pack_string ("check_name", 274 mi->check_name), 275 GNUNET_JSON_pack_allow_null ( 276 GNUNET_JSON_pack_string ("prog_name", 277 mi->prog_name)), 278 GNUNET_JSON_pack_allow_null ( 279 GNUNET_JSON_pack_object_incref ("context", 280 (json_t *) mi->context)) 281 ); 282 GNUNET_break (0 == 283 json_object_set_new (jmeasures, 284 mi->measure_name, 285 measure)); 286 } 287 288 return GNUNET_JSON_PACK ( 289 GNUNET_JSON_pack_timestamp ("expiration_time", 290 expiration_time), 291 GNUNET_JSON_pack_allow_null ( 292 GNUNET_JSON_pack_string ("successor_measure", 293 successor_measure)), 294 GNUNET_JSON_pack_array_steal ("rules", 295 jrules), 296 GNUNET_JSON_pack_object_steal ("custom_measures", 297 jmeasures) 298 ); 299 } 300 301 302 struct TALER_EXCHANGE_PostAmlDecisionHandle * 303 TALER_EXCHANGE_post_aml_decision_create ( 304 struct GNUNET_CURL_Context *ctx, 305 const char *url, 306 const struct TALER_NormalizedPaytoHashP *h_payto, 307 struct GNUNET_TIME_Timestamp decision_time, 308 const char *successor_measure, 309 struct GNUNET_TIME_Timestamp expiration_time, 310 unsigned int num_rules, 311 const struct TALER_EXCHANGE_AccountRule rules[static num_rules], 312 unsigned int num_measures, 313 const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures], 314 bool keep_investigating, 315 const char *justification, 316 const struct TALER_AmlOfficerPrivateKeyP *officer_priv) 317 { 318 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh; 319 json_t *new_rules; 320 321 new_rules = build_new_rules (successor_measure, 322 expiration_time, 323 num_rules, 324 rules, 325 num_measures, 326 measures); 327 if (NULL == new_rules) 328 { 329 GNUNET_break (0); 330 return NULL; 331 } 332 333 padh = GNUNET_new (struct TALER_EXCHANGE_PostAmlDecisionHandle); 334 padh->ctx = ctx; 335 padh->base_url = GNUNET_strdup (url); 336 padh->h_payto = *h_payto; 337 padh->decision_time = decision_time; 338 padh->justification = GNUNET_strdup (justification); 339 padh->keep_investigating = keep_investigating; 340 padh->new_rules = new_rules; 341 padh->officer_priv = *officer_priv; 342 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 343 &padh->officer_pub.eddsa_pub); 344 return padh; 345 } 346 347 348 enum GNUNET_GenericReturnValue 349 TALER_EXCHANGE_post_aml_decision_set_options_ ( 350 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh, 351 unsigned int num_options, 352 const struct TALER_EXCHANGE_PostAmlDecisionOptionValue options[]) 353 { 354 for (unsigned int i = 0; i < num_options; i++) 355 { 356 const struct TALER_EXCHANGE_PostAmlDecisionOptionValue *opt = &options[i]; 357 358 switch (opt->option) 359 { 360 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_END: 361 return GNUNET_OK; 362 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PAYTO_URI: 363 GNUNET_free (padh->payto_uri_str); 364 padh->payto_uri_str = 365 (NULL != opt->details.payto_uri.full_payto) 366 ? GNUNET_strdup (opt->details.payto_uri.full_payto) 367 : NULL; 368 break; 369 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_NEW_MEASURES: 370 GNUNET_free (padh->new_measures); 371 padh->new_measures = 372 (NULL != opt->details.new_measures) 373 ? GNUNET_strdup (opt->details.new_measures) 374 : NULL; 375 break; 376 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PROPERTIES: 377 if (NULL != padh->properties) 378 json_decref (padh->properties); 379 padh->properties = 380 (NULL != opt->details.properties) 381 ? json_incref ((json_t *) opt->details.properties) 382 : NULL; 383 break; 384 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_EVENTS: 385 { 386 if (NULL != padh->jevents) 387 json_decref (padh->jevents); 388 if (0 == opt->details.events.num_events) 389 { 390 padh->jevents = NULL; 391 } 392 else 393 { 394 padh->jevents = json_array (); 395 GNUNET_assert (NULL != padh->jevents); 396 for (unsigned int j = 0; j < opt->details.events.num_events; j++) 397 GNUNET_assert (0 == 398 json_array_append_new ( 399 padh->jevents, 400 json_string (opt->details.events.events[j]))); 401 } 402 } 403 break; 404 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_ATTRIBUTES: 405 if (NULL != padh->attributes) 406 json_decref (padh->attributes); 407 padh->attributes = 408 (NULL != opt->details.attributes) 409 ? json_incref ((json_t *) opt->details.attributes) 410 : NULL; 411 break; 412 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_ATTRIBUTES_EXPIRATION: 413 padh->attributes_expiration = opt->details.attributes_expiration; 414 break; 415 default: 416 GNUNET_break (0); 417 return GNUNET_SYSERR; 418 } 419 } 420 return GNUNET_OK; 421 } 422 423 424 enum TALER_ErrorCode 425 TALER_EXCHANGE_post_aml_decision_start ( 426 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh, 427 TALER_EXCHANGE_PostAmlDecisionCallback cb, 428 TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls) 429 { 430 CURL *eh; 431 struct TALER_AmlOfficerSignatureP officer_sig; 432 json_t *body; 433 char *path; 434 char opus[sizeof (padh->officer_pub) * 2]; 435 char *end; 436 struct TALER_FullPayto payto_uri_val = { 437 .full_payto = padh->payto_uri_str 438 }; 439 440 padh->cb = cb; 441 padh->cb_cls = cb_cls; 442 TALER_officer_aml_decision_sign (padh->justification, 443 padh->decision_time, 444 &padh->h_payto, 445 padh->new_rules, 446 padh->properties, 447 padh->new_measures, 448 padh->keep_investigating, 449 &padh->officer_priv, 450 &officer_sig); 451 452 end = GNUNET_STRINGS_data_to_string ( 453 &padh->officer_pub, 454 sizeof (padh->officer_pub), 455 opus, 456 sizeof (opus)); 457 *end = '\0'; 458 GNUNET_asprintf (&path, 459 "aml/%s/decision", 460 opus); 461 padh->url = TALER_url_join (padh->base_url, 462 path, 463 NULL); 464 GNUNET_free (path); 465 if (NULL == padh->url) 466 { 467 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 468 "Could not construct request URL.\n"); 469 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 470 } 471 472 body = GNUNET_JSON_PACK ( 473 GNUNET_JSON_pack_string ("justification", 474 padh->justification), 475 GNUNET_JSON_pack_data_auto ("h_payto", 476 &padh->h_payto), 477 GNUNET_JSON_pack_allow_null ( 478 TALER_JSON_pack_full_payto ("payto_uri", 479 payto_uri_val)), 480 GNUNET_JSON_pack_object_incref ("new_rules", 481 padh->new_rules), 482 GNUNET_JSON_pack_allow_null ( 483 GNUNET_JSON_pack_object_incref ("properties", 484 padh->properties)), 485 GNUNET_JSON_pack_allow_null ( 486 GNUNET_JSON_pack_string ("new_measures", 487 padh->new_measures)), 488 GNUNET_JSON_pack_bool ("keep_investigating", 489 padh->keep_investigating), 490 GNUNET_JSON_pack_data_auto ("officer_sig", 491 &officer_sig), 492 GNUNET_JSON_pack_timestamp ("decision_time", 493 padh->decision_time), 494 GNUNET_JSON_pack_allow_null ( 495 GNUNET_JSON_pack_array_incref ("events", 496 padh->jevents)), 497 GNUNET_JSON_pack_allow_null ( 498 GNUNET_JSON_pack_object_incref ("attributes", 499 padh->attributes)) 500 ); 501 if (NULL != padh->attributes) 502 { 503 GNUNET_assert ( 504 0 == 505 json_object_set_new ( 506 body, 507 "attributes_expiration", 508 GNUNET_JSON_from_timestamp (padh->attributes_expiration))); 509 } 510 511 eh = TALER_EXCHANGE_curl_easy_get_ (padh->url); 512 if ( (NULL == eh) || 513 (GNUNET_OK != 514 TALER_curl_easy_post (&padh->post_ctx, 515 eh, 516 body)) ) 517 { 518 GNUNET_break (0); 519 if (NULL != eh) 520 curl_easy_cleanup (eh); 521 json_decref (body); 522 GNUNET_free (padh->url); 523 padh->url = NULL; 524 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 525 } 526 json_decref (body); 527 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 528 "Requesting URL '%s'\n", 529 padh->url); 530 padh->job = GNUNET_CURL_job_add2 (padh->ctx, 531 eh, 532 padh->post_ctx.headers, 533 &handle_post_aml_decision_finished, 534 padh); 535 if (NULL == padh->job) 536 { 537 TALER_curl_easy_post_finished (&padh->post_ctx); 538 GNUNET_free (padh->url); 539 padh->url = NULL; 540 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 541 } 542 return TALER_EC_NONE; 543 } 544 545 546 void 547 TALER_EXCHANGE_post_aml_decision_cancel ( 548 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh) 549 { 550 if (NULL != padh->job) 551 { 552 GNUNET_CURL_job_cancel (padh->job); 553 padh->job = NULL; 554 } 555 TALER_curl_easy_post_finished (&padh->post_ctx); 556 json_decref (padh->new_rules); 557 if (NULL != padh->properties) 558 json_decref (padh->properties); 559 if (NULL != padh->jevents) 560 json_decref (padh->jevents); 561 if (NULL != padh->attributes) 562 json_decref (padh->attributes); 563 GNUNET_free (padh->url); 564 GNUNET_free (padh->base_url); 565 GNUNET_free (padh->justification); 566 GNUNET_free (padh->payto_uri_str); 567 GNUNET_free (padh->new_measures); 568 GNUNET_free (padh); 569 } 570 571 572 /* end of exchange_api_post-aml-OFFICER_PUB-decision.c */