testing_api_cmd_take_aml_decision.c (15362B)
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 6 under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_take_aml_decision.c 21 * @brief command for testing /aml/$OFFICER_PUB/decision 22 * @author Christian Grothoff 23 */ 24 #include "taler/platform.h" 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 28 /** 29 * State for a "take_aml_decision" CMD. 30 */ 31 struct AmlDecisionState; 32 33 #define TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE \ 34 struct AmlDecisionState 35 #include "taler/taler-exchange/post-aml-OFFICER_PUB-decision.h" 36 #include "taler/taler_testing_lib.h" 37 #include "taler/taler_signatures.h" 38 #include "taler/backoff.h" 39 40 41 /** 42 * State for a "take_aml_decision" CMD. 43 */ 44 struct AmlDecisionState 45 { 46 47 /** 48 * Handle while operation is running. 49 */ 50 struct TALER_EXCHANGE_PostAmlDecisionHandle *dh; 51 52 /** 53 * Our interpreter. 54 */ 55 struct TALER_TESTING_Interpreter *is; 56 57 /** 58 * Reference to command to previous set officer command that gives 59 * us an officer_priv trait. 60 */ 61 const char *officer_ref_cmd; 62 63 /** 64 * Reference to command to previous AML-triggering event that gives 65 * us a payto-hash trait. 66 */ 67 const char *account_ref_cmd; 68 69 /** 70 * Payto hash of the account we are manipulating the AML settings for. 71 */ 72 struct TALER_NormalizedPaytoHashP h_payto; 73 74 /** 75 * Justification given. 76 */ 77 const char *justification; 78 79 /** 80 * Delay to apply to compute the expiration time 81 * for the rules. 82 */ 83 struct GNUNET_TIME_Relative expiration_delay; 84 85 /** 86 * Successor measure to activate upon expiration. 87 */ 88 const char *successor_measure; 89 90 /** 91 * True to keep AML investigation open. 92 */ 93 bool keep_investigating; 94 95 /** 96 * New rules to enforce. 97 */ 98 json_t *new_rules; 99 100 /** 101 * Account properties to set. 102 */ 103 json_t *properties; 104 105 /** 106 * Expected response code. 107 */ 108 unsigned int expected_response; 109 }; 110 111 112 /** 113 * Callback to analyze the /aml-decision/$OFFICER_PUB response, just used to 114 * check if the response code is acceptable. 115 * 116 * @param ds our state 117 * @param pr response details 118 */ 119 static void 120 take_aml_decision_cb ( 121 struct AmlDecisionState *ds, 122 const struct TALER_EXCHANGE_PostAmlDecisionResponse *pr) 123 { 124 const struct TALER_EXCHANGE_HttpResponse *hr = &pr->hr; 125 126 ds->dh = NULL; 127 if (ds->expected_response != hr->http_status) 128 { 129 TALER_TESTING_unexpected_status (ds->is, 130 hr->http_status, 131 ds->expected_response); 132 return; 133 } 134 TALER_TESTING_interpreter_next (ds->is); 135 } 136 137 138 /** 139 * Run the command. 140 * 141 * @param cls closure. 142 * @param cmd the command to execute. 143 * @param is the interpreter state. 144 */ 145 static void 146 take_aml_decision_run (void *cls, 147 const struct TALER_TESTING_Command *cmd, 148 struct TALER_TESTING_Interpreter *is) 149 { 150 struct AmlDecisionState *ds = cls; 151 struct GNUNET_TIME_Timestamp now; 152 const struct TALER_NormalizedPaytoHashP *h_payto; 153 const struct TALER_AmlOfficerPrivateKeyP *officer_priv; 154 const struct TALER_TESTING_Command *ref; 155 const char *exchange_url; 156 const json_t *jrules; 157 const json_t *jmeasures = NULL; 158 struct GNUNET_TIME_Timestamp expiration_time 159 = GNUNET_TIME_relative_to_timestamp (ds->expiration_delay); 160 const char *new_measures = NULL; 161 struct GNUNET_JSON_Specification spec[] = { 162 GNUNET_JSON_spec_array_const ("rules", 163 &jrules), 164 GNUNET_JSON_spec_mark_optional ( 165 GNUNET_JSON_spec_object_const ("custom_measures", 166 &jmeasures), 167 NULL), 168 GNUNET_JSON_spec_mark_optional ( 169 GNUNET_JSON_spec_string ("new_measures", 170 &new_measures), 171 NULL), 172 GNUNET_JSON_spec_end () 173 }; 174 unsigned int num_rules; 175 unsigned int num_measures; 176 177 (void) cmd; 178 if (GNUNET_OK != 179 GNUNET_JSON_parse (ds->new_rules, 180 spec, 181 NULL, NULL)) 182 { 183 GNUNET_break_op (0); 184 TALER_TESTING_interpreter_fail (is); 185 return; 186 } 187 188 { 189 const struct TALER_TESTING_Command *exchange_cmd; 190 191 exchange_cmd = TALER_TESTING_interpreter_get_command (is, 192 "exchange"); 193 if (NULL == exchange_cmd) 194 { 195 GNUNET_break (0); 196 TALER_TESTING_interpreter_fail (is); 197 return; 198 } 199 GNUNET_assert (GNUNET_OK == 200 TALER_TESTING_get_trait_exchange_url (exchange_cmd, 201 &exchange_url)); 202 } 203 now = GNUNET_TIME_timestamp_get (); 204 ds->is = is; 205 ref = TALER_TESTING_interpreter_lookup_command (is, 206 ds->account_ref_cmd); 207 if (NULL == ref) 208 { 209 GNUNET_break (0); 210 TALER_TESTING_interpreter_fail (is); 211 return; 212 } 213 if (GNUNET_OK != 214 TALER_TESTING_get_trait_h_normalized_payto (ref, 215 &h_payto)) 216 { 217 GNUNET_break (0); 218 TALER_TESTING_interpreter_fail (is); 219 return; 220 } 221 ref = TALER_TESTING_interpreter_lookup_command (is, 222 ds->officer_ref_cmd); 223 if (NULL == ref) 224 { 225 GNUNET_break (0); 226 TALER_TESTING_interpreter_fail (is); 227 return; 228 } 229 if (GNUNET_OK != 230 TALER_TESTING_get_trait_officer_priv (ref, 231 &officer_priv)) 232 { 233 GNUNET_break (0); 234 TALER_TESTING_interpreter_fail (is); 235 return; 236 } 237 ds->h_payto = *h_payto; 238 239 num_rules = (unsigned int) json_array_size (jrules); 240 num_measures = (unsigned int) json_object_size (jmeasures); 241 { 242 struct TALER_EXCHANGE_AccountRule rules[ 243 GNUNET_NZL (num_rules)]; 244 struct TALER_EXCHANGE_MeasureInformation measures[ 245 GNUNET_NZL (num_measures)]; 246 const json_t *jrule; 247 size_t i; 248 const json_t *jmeasure; 249 const char *mname; 250 unsigned int off; 251 252 memset (rules, 253 0, 254 sizeof (rules)); 255 memset (measures, 256 0, 257 sizeof (measures)); 258 json_array_foreach ((json_t *) jrules, i, jrule) 259 { 260 struct TALER_EXCHANGE_AccountRule *rule = &rules[i]; 261 const json_t *jameasures = NULL; 262 struct GNUNET_JSON_Specification ispec[] = { 263 GNUNET_JSON_spec_relative_time ("timeframe", 264 &rule->timeframe), 265 TALER_JSON_spec_amount_any ("threshold", 266 &rule->threshold), 267 GNUNET_JSON_spec_mark_optional ( 268 GNUNET_JSON_spec_array_const ("measures", 269 &jameasures), 270 NULL), 271 GNUNET_JSON_spec_mark_optional ( 272 GNUNET_JSON_spec_uint32 ("display_priority", 273 &rule->display_priority), 274 NULL), 275 TALER_JSON_spec_kycte ("operation_type", 276 &rule->operation_type), 277 GNUNET_JSON_spec_mark_optional ( 278 GNUNET_JSON_spec_bool ("verboten", 279 &rule->verboten), 280 NULL), 281 GNUNET_JSON_spec_mark_optional ( 282 GNUNET_JSON_spec_bool ("exposed", 283 &rule->exposed), 284 NULL), 285 GNUNET_JSON_spec_mark_optional ( 286 GNUNET_JSON_spec_bool ("is_and_combinator", 287 &rule->is_and_combinator), 288 NULL), 289 GNUNET_JSON_spec_end () 290 }; 291 const char *err_name; 292 unsigned int err_line; 293 294 if (GNUNET_OK != 295 GNUNET_JSON_parse (jrule, 296 ispec, 297 &err_name, 298 &err_line)) 299 { 300 GNUNET_break_op (0); 301 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 302 "Malformed rule #%u in field %s\n", 303 (unsigned int) i, 304 err_name); 305 TALER_TESTING_interpreter_fail (is); 306 return; 307 } 308 if (NULL != jameasures) 309 { 310 rule->num_measures 311 = (unsigned int) json_array_size (jameasures); 312 rule->measures 313 = GNUNET_new_array (rule->num_measures, 314 const char *); 315 for (unsigned int k = 0; k < rule->num_measures; k++) 316 rule->measures[k] 317 = json_string_value ( 318 json_array_get (jameasures, 319 k)); 320 } 321 } 322 323 off = 0; 324 json_object_foreach ((json_t *) jmeasures, mname, jmeasure) 325 { 326 struct TALER_EXCHANGE_MeasureInformation *mi = &measures[off++]; 327 struct GNUNET_JSON_Specification ispec[] = { 328 GNUNET_JSON_spec_mark_optional ( 329 GNUNET_JSON_spec_string ("check_name", 330 &mi->check_name), 331 NULL), 332 GNUNET_JSON_spec_mark_optional ( 333 GNUNET_JSON_spec_string ("prog_name", 334 &mi->prog_name), 335 NULL), 336 GNUNET_JSON_spec_mark_optional ( 337 GNUNET_JSON_spec_object_const ("context", 338 &mi->context), 339 NULL), 340 GNUNET_JSON_spec_end () 341 }; 342 const char *err_name; 343 unsigned int err_line; 344 345 mi->measure_name = mname; 346 if (GNUNET_OK != 347 GNUNET_JSON_parse (jmeasure, 348 ispec, 349 &err_name, 350 &err_line)) 351 { 352 GNUNET_break_op (0); 353 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 354 "Malformed measure %s in field %s\n", 355 mname, 356 err_name); 357 TALER_TESTING_interpreter_fail (is); 358 for (unsigned int j = 0; j < num_rules; j++) 359 GNUNET_free (rules[j].measures); 360 return; 361 } 362 } 363 GNUNET_assert (off == num_measures); 364 365 ds->dh = TALER_EXCHANGE_post_aml_decision_create ( 366 TALER_TESTING_interpreter_get_context (is), 367 exchange_url, 368 h_payto, 369 now, 370 ds->successor_measure, 371 expiration_time, 372 num_rules, 373 rules, 374 num_measures, 375 measures, 376 ds->keep_investigating, 377 ds->justification, 378 officer_priv); 379 380 for (unsigned int j = 0; j < num_rules; j++) 381 GNUNET_free (rules[j].measures); 382 383 if (NULL == ds->dh) 384 { 385 GNUNET_break (0); 386 TALER_TESTING_interpreter_fail (is); 387 return; 388 } 389 390 /* Set optional: new_measures and properties */ 391 if (NULL != new_measures) 392 TALER_EXCHANGE_post_aml_decision_set_options ( 393 ds->dh, 394 TALER_EXCHANGE_post_aml_decision_option_new_measures (new_measures)); 395 if (NULL != ds->properties) 396 TALER_EXCHANGE_post_aml_decision_set_options ( 397 ds->dh, 398 TALER_EXCHANGE_post_aml_decision_option_properties (ds->properties)); 399 400 { 401 enum TALER_ErrorCode ec; 402 403 ec = TALER_EXCHANGE_post_aml_decision_start (ds->dh, 404 &take_aml_decision_cb, 405 ds); 406 if (TALER_EC_NONE != ec) 407 { 408 GNUNET_break (0); 409 ds->dh = NULL; 410 TALER_TESTING_interpreter_fail (is); 411 return; 412 } 413 } 414 } 415 } 416 417 418 /** 419 * Free the state of a "take_aml_decision" CMD, and possibly cancel a 420 * pending operation thereof. 421 * 422 * @param cls closure, must be a `struct AmlDecisionState`. 423 * @param cmd the command which is being cleaned up. 424 */ 425 static void 426 take_aml_decision_cleanup (void *cls, 427 const struct TALER_TESTING_Command *cmd) 428 { 429 struct AmlDecisionState *ds = cls; 430 431 if (NULL != ds->dh) 432 { 433 TALER_TESTING_command_incomplete (ds->is, 434 cmd->label); 435 TALER_EXCHANGE_post_aml_decision_cancel (ds->dh); 436 ds->dh = NULL; 437 } 438 json_decref (ds->new_rules); 439 json_decref (ds->properties); 440 GNUNET_free (ds); 441 } 442 443 444 /** 445 * Offer internal data of a "AML decision" CMD state to other 446 * commands. 447 * 448 * @param cls closure 449 * @param[out] ret result (could be anything) 450 * @param trait name of the trait 451 * @param index index number of the object to offer. 452 * @return #GNUNET_OK on success 453 */ 454 static enum GNUNET_GenericReturnValue 455 take_aml_decision_traits (void *cls, 456 const void **ret, 457 const char *trait, 458 unsigned int index) 459 { 460 struct AmlDecisionState *ws = cls; 461 struct TALER_TESTING_Trait traits[] = { 462 TALER_TESTING_make_trait_h_normalized_payto (&ws->h_payto), 463 TALER_TESTING_make_trait_aml_justification (ws->justification), 464 TALER_TESTING_trait_end () 465 }; 466 467 return TALER_TESTING_get_trait (traits, 468 ret, 469 trait, 470 index); 471 } 472 473 474 struct TALER_TESTING_Command 475 TALER_TESTING_cmd_take_aml_decision ( 476 const char *label, 477 const char *ref_officer, 478 const char *ref_operation, 479 bool keep_investigating, 480 struct GNUNET_TIME_Relative expiration_delay, 481 const char *successor_measure, 482 const char *new_rules, 483 const char *properties, 484 const char *justification, 485 unsigned int expected_response) 486 { 487 struct AmlDecisionState *ds; 488 json_error_t err; 489 490 ds = GNUNET_new (struct AmlDecisionState); 491 ds->officer_ref_cmd = ref_officer; 492 ds->account_ref_cmd = ref_operation; 493 ds->keep_investigating = keep_investigating; 494 ds->expiration_delay = expiration_delay; 495 ds->successor_measure = successor_measure; 496 ds->new_rules = json_loads (new_rules, 497 JSON_DECODE_ANY, 498 &err); 499 if (NULL == ds->new_rules) 500 { 501 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 502 "Invalid JSON in new rules of %s: %s\n", 503 label, 504 err.text); 505 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 506 "Input was: `%s'\n", 507 new_rules); 508 GNUNET_assert (0); 509 } 510 GNUNET_assert (NULL != ds->new_rules); 511 ds->properties = json_loads (properties, 512 0, 513 &err); 514 if (NULL == ds->properties) 515 { 516 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 517 "Invalid JSON in properties of %s: %s\n", 518 label, 519 err.text); 520 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 521 "Input was: `%s'\n", 522 properties); 523 GNUNET_assert (0); 524 } 525 ds->justification = justification; 526 ds->expected_response = expected_response; 527 { 528 struct TALER_TESTING_Command cmd = { 529 .cls = ds, 530 .label = label, 531 .run = &take_aml_decision_run, 532 .cleanup = &take_aml_decision_cleanup, 533 .traits = &take_aml_decision_traits 534 }; 535 536 return cmd; 537 } 538 } 539 540 541 /* end of testing_api_cmd_take_aml_decision.c */