exchange_api_post-aml-OFFICER_PUB-decision.c (16064B)
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 134 135 /** 136 * Function called when we're done processing the 137 * HTTP POST /aml/$OFFICER_PUB/decision request. 138 * 139 * @param cls the `struct TALER_EXCHANGE_PostAmlDecisionHandle *` 140 * @param response_code HTTP response code, 0 on error 141 * @param response response body, NULL if not in JSON 142 */ 143 static void 144 handle_post_aml_decision_finished (void *cls, 145 long response_code, 146 const void *response) 147 { 148 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh = cls; 149 const json_t *json = response; 150 struct TALER_EXCHANGE_PostAmlDecisionResponse pr = { 151 .hr.http_status = (unsigned int) response_code, 152 .hr.reply = json 153 }; 154 155 padh->job = NULL; 156 switch (response_code) 157 { 158 case 0: 159 pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 160 pr.hr.hint = "server offline?"; 161 break; 162 case MHD_HTTP_NO_CONTENT: 163 break; 164 case MHD_HTTP_FORBIDDEN: 165 pr.hr.ec = TALER_JSON_get_error_code (json); 166 pr.hr.hint = TALER_JSON_get_error_hint (json); 167 break; 168 case MHD_HTTP_CONFLICT: 169 pr.hr.ec = TALER_JSON_get_error_code (json); 170 pr.hr.hint = TALER_JSON_get_error_hint (json); 171 break; 172 default: 173 GNUNET_break_op (0); 174 pr.hr.ec = TALER_JSON_get_error_code (json); 175 pr.hr.hint = TALER_JSON_get_error_hint (json); 176 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 177 "Unexpected response code %u/%d for POST AML decision\n", 178 (unsigned int) response_code, 179 (int) pr.hr.ec); 180 break; 181 } 182 if (NULL != padh->cb) 183 { 184 padh->cb (padh->cb_cls, 185 &pr); 186 padh->cb = NULL; 187 } 188 TALER_EXCHANGE_post_aml_decision_cancel (padh); 189 } 190 191 192 /** 193 * Build the new_rules JSON object from rules and measures arrays. 194 * 195 * @param successor_measure optional successor measure name 196 * @param expiration_time when the new rules expire 197 * @param num_rules length of @a rules 198 * @param rules the rules array 199 * @param num_measures length of @a measures 200 * @param measures the measures array 201 * @return new JSON object (caller owns reference), NULL on error 202 */ 203 static json_t * 204 build_new_rules ( 205 const char *successor_measure, 206 struct GNUNET_TIME_Timestamp expiration_time, 207 unsigned int num_rules, 208 const struct TALER_EXCHANGE_AccountRule rules[static num_rules], 209 unsigned int num_measures, 210 const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures]) 211 { 212 json_t *jrules; 213 json_t *jmeasures; 214 215 jrules = json_array (); 216 GNUNET_assert (NULL != jrules); 217 for (unsigned int i = 0; i < num_rules; i++) 218 { 219 const struct TALER_EXCHANGE_AccountRule *al = &rules[i]; 220 json_t *ameasures; 221 json_t *rule; 222 223 ameasures = json_array (); 224 GNUNET_assert (NULL != ameasures); 225 for (unsigned int j = 0; j < al->num_measures; j++) 226 GNUNET_assert (0 == 227 json_array_append_new (ameasures, 228 json_string (al->measures[j]))); 229 rule = GNUNET_JSON_PACK ( 230 TALER_JSON_pack_kycte ("operation_type", 231 al->operation_type), 232 TALER_JSON_pack_amount ("threshold", 233 &al->threshold), 234 GNUNET_JSON_pack_time_rel ("timeframe", 235 al->timeframe), 236 GNUNET_JSON_pack_array_steal ("measures", 237 ameasures), 238 GNUNET_JSON_pack_bool ("exposed", 239 al->exposed), 240 GNUNET_JSON_pack_bool ("is_and_combinator", 241 al->is_and_combinator), 242 GNUNET_JSON_pack_uint64 ("display_priority", 243 al->display_priority) 244 ); 245 GNUNET_break (0 == 246 json_array_append_new (jrules, 247 rule)); 248 } 249 250 jmeasures = json_object (); 251 GNUNET_assert (NULL != jmeasures); 252 for (unsigned int i = 0; i < num_measures; i++) 253 { 254 const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i]; 255 json_t *measure; 256 257 measure = GNUNET_JSON_PACK ( 258 GNUNET_JSON_pack_string ("check_name", 259 mi->check_name), 260 GNUNET_JSON_pack_allow_null ( 261 GNUNET_JSON_pack_string ("prog_name", 262 mi->prog_name)), 263 GNUNET_JSON_pack_allow_null ( 264 GNUNET_JSON_pack_object_incref ("context", 265 (json_t *) mi->context)) 266 ); 267 GNUNET_break (0 == 268 json_object_set_new (jmeasures, 269 mi->measure_name, 270 measure)); 271 } 272 273 return GNUNET_JSON_PACK ( 274 GNUNET_JSON_pack_timestamp ("expiration_time", 275 expiration_time), 276 GNUNET_JSON_pack_allow_null ( 277 GNUNET_JSON_pack_string ("successor_measure", 278 successor_measure)), 279 GNUNET_JSON_pack_array_steal ("rules", 280 jrules), 281 GNUNET_JSON_pack_object_steal ("custom_measures", 282 jmeasures) 283 ); 284 } 285 286 287 struct TALER_EXCHANGE_PostAmlDecisionHandle * 288 TALER_EXCHANGE_post_aml_decision_create ( 289 struct GNUNET_CURL_Context *ctx, 290 const char *url, 291 const struct TALER_NormalizedPaytoHashP *h_payto, 292 struct GNUNET_TIME_Timestamp decision_time, 293 const char *successor_measure, 294 struct GNUNET_TIME_Timestamp expiration_time, 295 unsigned int num_rules, 296 const struct TALER_EXCHANGE_AccountRule rules[static num_rules], 297 unsigned int num_measures, 298 const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures], 299 bool keep_investigating, 300 const char *justification, 301 const struct TALER_AmlOfficerPrivateKeyP *officer_priv) 302 { 303 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh; 304 json_t *new_rules; 305 306 new_rules = build_new_rules (successor_measure, 307 expiration_time, 308 num_rules, 309 rules, 310 num_measures, 311 measures); 312 if (NULL == new_rules) 313 { 314 GNUNET_break (0); 315 return NULL; 316 } 317 318 padh = GNUNET_new (struct TALER_EXCHANGE_PostAmlDecisionHandle); 319 padh->ctx = ctx; 320 padh->base_url = GNUNET_strdup (url); 321 padh->h_payto = *h_payto; 322 padh->decision_time = decision_time; 323 padh->justification = GNUNET_strdup (justification); 324 padh->keep_investigating = keep_investigating; 325 padh->new_rules = new_rules; 326 padh->officer_priv = *officer_priv; 327 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 328 &padh->officer_pub.eddsa_pub); 329 return padh; 330 } 331 332 333 enum GNUNET_GenericReturnValue 334 TALER_EXCHANGE_post_aml_decision_set_options_ ( 335 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh, 336 unsigned int num_options, 337 const struct TALER_EXCHANGE_PostAmlDecisionOptionValue options[]) 338 { 339 for (unsigned int i = 0; i < num_options; i++) 340 { 341 const struct TALER_EXCHANGE_PostAmlDecisionOptionValue *opt = &options[i]; 342 343 switch (opt->option) 344 { 345 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_END: 346 return GNUNET_OK; 347 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PAYTO_URI: 348 GNUNET_free (padh->payto_uri_str); 349 padh->payto_uri_str = 350 (NULL != opt->details.payto_uri.full_payto) 351 ? GNUNET_strdup (opt->details.payto_uri.full_payto) 352 : NULL; 353 break; 354 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_NEW_MEASURES: 355 GNUNET_free (padh->new_measures); 356 padh->new_measures = 357 (NULL != opt->details.new_measures) 358 ? GNUNET_strdup (opt->details.new_measures) 359 : NULL; 360 break; 361 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PROPERTIES: 362 if (NULL != padh->properties) 363 json_decref (padh->properties); 364 padh->properties = 365 (NULL != opt->details.properties) 366 ? json_incref ((json_t *) opt->details.properties) 367 : NULL; 368 break; 369 case TALER_EXCHANGE_POST_AML_DECISION_OPTION_EVENTS: 370 { 371 if (NULL != padh->jevents) 372 json_decref (padh->jevents); 373 if (0 == opt->details.events.num_events) 374 { 375 padh->jevents = NULL; 376 } 377 else 378 { 379 padh->jevents = json_array (); 380 GNUNET_assert (NULL != padh->jevents); 381 for (unsigned int j = 0; j < opt->details.events.num_events; j++) 382 GNUNET_assert (0 == 383 json_array_append_new ( 384 padh->jevents, 385 json_string (opt->details.events.events[j]))); 386 } 387 } 388 break; 389 default: 390 GNUNET_break (0); 391 return GNUNET_SYSERR; 392 } 393 } 394 return GNUNET_OK; 395 } 396 397 398 enum TALER_ErrorCode 399 TALER_EXCHANGE_post_aml_decision_start ( 400 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh, 401 TALER_EXCHANGE_PostAmlDecisionCallback cb, 402 TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls) 403 { 404 CURL *eh; 405 struct TALER_AmlOfficerSignatureP officer_sig; 406 json_t *body; 407 char *path; 408 char opus[sizeof (padh->officer_pub) * 2]; 409 char *end; 410 struct TALER_FullPayto payto_uri_val = { 411 .full_payto = padh->payto_uri_str 412 }; 413 414 padh->cb = cb; 415 padh->cb_cls = cb_cls; 416 TALER_officer_aml_decision_sign (padh->justification, 417 padh->decision_time, 418 &padh->h_payto, 419 padh->new_rules, 420 padh->properties, 421 padh->new_measures, 422 padh->keep_investigating, 423 &padh->officer_priv, 424 &officer_sig); 425 426 end = GNUNET_STRINGS_data_to_string ( 427 &padh->officer_pub, 428 sizeof (padh->officer_pub), 429 opus, 430 sizeof (opus)); 431 *end = '\0'; 432 GNUNET_asprintf (&path, 433 "aml/%s/decision", 434 opus); 435 padh->url = TALER_url_join (padh->base_url, 436 path, 437 NULL); 438 GNUNET_free (path); 439 if (NULL == padh->url) 440 { 441 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 442 "Could not construct request URL.\n"); 443 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 444 } 445 446 body = GNUNET_JSON_PACK ( 447 GNUNET_JSON_pack_string ("justification", 448 padh->justification), 449 GNUNET_JSON_pack_data_auto ("h_payto", 450 &padh->h_payto), 451 GNUNET_JSON_pack_allow_null ( 452 TALER_JSON_pack_full_payto ("payto_uri", 453 payto_uri_val)), 454 GNUNET_JSON_pack_object_incref ("new_rules", 455 padh->new_rules), 456 GNUNET_JSON_pack_allow_null ( 457 GNUNET_JSON_pack_object_incref ("properties", 458 padh->properties)), 459 GNUNET_JSON_pack_allow_null ( 460 GNUNET_JSON_pack_string ("new_measures", 461 padh->new_measures)), 462 GNUNET_JSON_pack_bool ("keep_investigating", 463 padh->keep_investigating), 464 GNUNET_JSON_pack_data_auto ("officer_sig", 465 &officer_sig), 466 GNUNET_JSON_pack_timestamp ("decision_time", 467 padh->decision_time), 468 GNUNET_JSON_pack_allow_null ( 469 GNUNET_JSON_pack_array_incref ("events", 470 padh->jevents)) 471 ); 472 473 eh = TALER_EXCHANGE_curl_easy_get_ (padh->url); 474 if ( (NULL == eh) || 475 (GNUNET_OK != 476 TALER_curl_easy_post (&padh->post_ctx, 477 eh, 478 body)) ) 479 { 480 GNUNET_break (0); 481 if (NULL != eh) 482 curl_easy_cleanup (eh); 483 json_decref (body); 484 GNUNET_free (padh->url); 485 padh->url = NULL; 486 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 487 } 488 json_decref (body); 489 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 490 "Requesting URL '%s'\n", 491 padh->url); 492 padh->job = GNUNET_CURL_job_add2 (padh->ctx, 493 eh, 494 padh->post_ctx.headers, 495 &handle_post_aml_decision_finished, 496 padh); 497 if (NULL == padh->job) 498 { 499 TALER_curl_easy_post_finished (&padh->post_ctx); 500 GNUNET_free (padh->url); 501 padh->url = NULL; 502 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 503 } 504 return TALER_EC_NONE; 505 } 506 507 508 void 509 TALER_EXCHANGE_post_aml_decision_cancel ( 510 struct TALER_EXCHANGE_PostAmlDecisionHandle *padh) 511 { 512 if (NULL != padh->job) 513 { 514 GNUNET_CURL_job_cancel (padh->job); 515 padh->job = NULL; 516 } 517 TALER_curl_easy_post_finished (&padh->post_ctx); 518 json_decref (padh->new_rules); 519 if (NULL != padh->properties) 520 json_decref (padh->properties); 521 if (NULL != padh->jevents) 522 json_decref (padh->jevents); 523 GNUNET_free (padh->url); 524 GNUNET_free (padh->base_url); 525 GNUNET_free (padh->justification); 526 GNUNET_free (padh->payto_uri_str); 527 GNUNET_free (padh->new_measures); 528 GNUNET_free (padh); 529 } 530 531 532 /* end of exchange_api_post-aml-OFFICER_PUB-decision.c */