Line data Source code
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 3 : take_aml_decision_cb (
121 : struct AmlDecisionState *ds,
122 : const struct TALER_EXCHANGE_PostAmlDecisionResponse *pr)
123 : {
124 3 : const struct TALER_EXCHANGE_HttpResponse *hr = &pr->hr;
125 :
126 3 : ds->dh = NULL;
127 3 : if (ds->expected_response != hr->http_status)
128 : {
129 0 : TALER_TESTING_unexpected_status (ds->is,
130 : hr->http_status,
131 : ds->expected_response);
132 0 : return;
133 : }
134 3 : 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 3 : take_aml_decision_run (void *cls,
147 : const struct TALER_TESTING_Command *cmd,
148 : struct TALER_TESTING_Interpreter *is)
149 : {
150 3 : 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 3 : const json_t *jmeasures = NULL;
158 : struct GNUNET_TIME_Timestamp expiration_time
159 3 : = GNUNET_TIME_relative_to_timestamp (ds->expiration_delay);
160 3 : const char *new_measures = NULL;
161 : struct GNUNET_JSON_Specification spec[] = {
162 3 : GNUNET_JSON_spec_array_const ("rules",
163 : &jrules),
164 3 : GNUNET_JSON_spec_mark_optional (
165 : GNUNET_JSON_spec_object_const ("custom_measures",
166 : &jmeasures),
167 : NULL),
168 3 : GNUNET_JSON_spec_mark_optional (
169 : GNUNET_JSON_spec_string ("new_measures",
170 : &new_measures),
171 : NULL),
172 3 : GNUNET_JSON_spec_end ()
173 : };
174 : unsigned int num_rules;
175 : unsigned int num_measures;
176 :
177 : (void) cmd;
178 3 : if (GNUNET_OK !=
179 3 : GNUNET_JSON_parse (ds->new_rules,
180 : spec,
181 : NULL, NULL))
182 : {
183 0 : GNUNET_break_op (0);
184 0 : TALER_TESTING_interpreter_fail (is);
185 0 : return;
186 : }
187 :
188 : {
189 : const struct TALER_TESTING_Command *exchange_cmd;
190 :
191 3 : exchange_cmd = TALER_TESTING_interpreter_get_command (is,
192 : "exchange");
193 3 : if (NULL == exchange_cmd)
194 : {
195 0 : GNUNET_break (0);
196 0 : TALER_TESTING_interpreter_fail (is);
197 0 : return;
198 : }
199 3 : GNUNET_assert (GNUNET_OK ==
200 : TALER_TESTING_get_trait_exchange_url (exchange_cmd,
201 : &exchange_url));
202 : }
203 3 : now = GNUNET_TIME_timestamp_get ();
204 3 : ds->is = is;
205 3 : ref = TALER_TESTING_interpreter_lookup_command (is,
206 : ds->account_ref_cmd);
207 3 : if (NULL == ref)
208 : {
209 0 : GNUNET_break (0);
210 0 : TALER_TESTING_interpreter_fail (is);
211 0 : return;
212 : }
213 3 : if (GNUNET_OK !=
214 3 : TALER_TESTING_get_trait_h_normalized_payto (ref,
215 : &h_payto))
216 : {
217 0 : GNUNET_break (0);
218 0 : TALER_TESTING_interpreter_fail (is);
219 0 : return;
220 : }
221 3 : ref = TALER_TESTING_interpreter_lookup_command (is,
222 : ds->officer_ref_cmd);
223 3 : if (NULL == ref)
224 : {
225 0 : GNUNET_break (0);
226 0 : TALER_TESTING_interpreter_fail (is);
227 0 : return;
228 : }
229 3 : if (GNUNET_OK !=
230 3 : TALER_TESTING_get_trait_officer_priv (ref,
231 : &officer_priv))
232 : {
233 0 : GNUNET_break (0);
234 0 : TALER_TESTING_interpreter_fail (is);
235 0 : return;
236 : }
237 3 : ds->h_payto = *h_payto;
238 :
239 3 : num_rules = (unsigned int) json_array_size (jrules);
240 3 : num_measures = (unsigned int) json_object_size (jmeasures);
241 3 : {
242 3 : struct TALER_EXCHANGE_AccountRule rules[
243 3 : GNUNET_NZL (num_rules)];
244 3 : struct TALER_EXCHANGE_MeasureInformation measures[
245 3 : 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 3 : memset (rules,
253 : 0,
254 : sizeof (rules));
255 3 : memset (measures,
256 : 0,
257 : sizeof (measures));
258 6 : json_array_foreach ((json_t *) jrules, i, jrule)
259 : {
260 3 : struct TALER_EXCHANGE_AccountRule *rule = &rules[i];
261 3 : const json_t *jameasures = NULL;
262 : struct GNUNET_JSON_Specification ispec[] = {
263 3 : GNUNET_JSON_spec_relative_time ("timeframe",
264 : &rule->timeframe),
265 3 : TALER_JSON_spec_amount_any ("threshold",
266 : &rule->threshold),
267 3 : GNUNET_JSON_spec_mark_optional (
268 : GNUNET_JSON_spec_array_const ("measures",
269 : &jameasures),
270 : NULL),
271 3 : GNUNET_JSON_spec_mark_optional (
272 : GNUNET_JSON_spec_uint32 ("display_priority",
273 : &rule->display_priority),
274 : NULL),
275 3 : TALER_JSON_spec_kycte ("operation_type",
276 : &rule->operation_type),
277 3 : GNUNET_JSON_spec_mark_optional (
278 : GNUNET_JSON_spec_bool ("verboten",
279 : &rule->verboten),
280 : NULL),
281 3 : GNUNET_JSON_spec_mark_optional (
282 : GNUNET_JSON_spec_bool ("exposed",
283 : &rule->exposed),
284 : NULL),
285 3 : GNUNET_JSON_spec_mark_optional (
286 : GNUNET_JSON_spec_bool ("is_and_combinator",
287 : &rule->is_and_combinator),
288 : NULL),
289 3 : GNUNET_JSON_spec_end ()
290 : };
291 : const char *err_name;
292 : unsigned int err_line;
293 :
294 3 : if (GNUNET_OK !=
295 3 : GNUNET_JSON_parse (jrule,
296 : ispec,
297 : &err_name,
298 : &err_line))
299 : {
300 0 : GNUNET_break_op (0);
301 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
302 : "Malformed rule #%u in field %s\n",
303 : (unsigned int) i,
304 : err_name);
305 0 : TALER_TESTING_interpreter_fail (is);
306 0 : return;
307 : }
308 3 : if (NULL != jameasures)
309 : {
310 : rule->num_measures
311 1 : = (unsigned int) json_array_size (jameasures);
312 : rule->measures
313 1 : = GNUNET_new_array (rule->num_measures,
314 : const char *);
315 2 : for (unsigned int k = 0; k < rule->num_measures; k++)
316 1 : rule->measures[k]
317 1 : = json_string_value (
318 1 : json_array_get (jameasures,
319 : k));
320 : }
321 : }
322 :
323 3 : off = 0;
324 4 : json_object_foreach ((json_t *) jmeasures, mname, jmeasure)
325 : {
326 1 : struct TALER_EXCHANGE_MeasureInformation *mi = &measures[off++];
327 : struct GNUNET_JSON_Specification ispec[] = {
328 1 : GNUNET_JSON_spec_mark_optional (
329 : GNUNET_JSON_spec_string ("check_name",
330 : &mi->check_name),
331 : NULL),
332 1 : GNUNET_JSON_spec_mark_optional (
333 : GNUNET_JSON_spec_string ("prog_name",
334 : &mi->prog_name),
335 : NULL),
336 1 : GNUNET_JSON_spec_mark_optional (
337 : GNUNET_JSON_spec_object_const ("context",
338 : &mi->context),
339 : NULL),
340 1 : GNUNET_JSON_spec_end ()
341 : };
342 : const char *err_name;
343 : unsigned int err_line;
344 :
345 1 : mi->measure_name = mname;
346 1 : if (GNUNET_OK !=
347 1 : GNUNET_JSON_parse (jmeasure,
348 : ispec,
349 : &err_name,
350 : &err_line))
351 : {
352 0 : GNUNET_break_op (0);
353 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
354 : "Malformed measure %s in field %s\n",
355 : mname,
356 : err_name);
357 0 : TALER_TESTING_interpreter_fail (is);
358 0 : for (unsigned int j = 0; j < num_rules; j++)
359 0 : GNUNET_free (rules[j].measures);
360 0 : return;
361 : }
362 : }
363 3 : GNUNET_assert (off == num_measures);
364 :
365 3 : 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 3 : ds->keep_investigating,
377 : ds->justification,
378 : officer_priv);
379 :
380 6 : for (unsigned int j = 0; j < num_rules; j++)
381 3 : GNUNET_free (rules[j].measures);
382 :
383 3 : if (NULL == ds->dh)
384 : {
385 0 : GNUNET_break (0);
386 0 : TALER_TESTING_interpreter_fail (is);
387 0 : return;
388 : }
389 :
390 : /* Set optional: new_measures and properties */
391 3 : if (NULL != new_measures)
392 1 : TALER_EXCHANGE_post_aml_decision_set_options (
393 : ds->dh,
394 : TALER_EXCHANGE_post_aml_decision_option_new_measures (new_measures));
395 3 : if (NULL != ds->properties)
396 3 : 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 3 : ec = TALER_EXCHANGE_post_aml_decision_start (ds->dh,
404 : &take_aml_decision_cb,
405 : ds);
406 3 : if (TALER_EC_NONE != ec)
407 : {
408 0 : GNUNET_break (0);
409 0 : ds->dh = NULL;
410 0 : TALER_TESTING_interpreter_fail (is);
411 0 : 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 3 : take_aml_decision_cleanup (void *cls,
427 : const struct TALER_TESTING_Command *cmd)
428 : {
429 3 : struct AmlDecisionState *ds = cls;
430 :
431 3 : if (NULL != ds->dh)
432 : {
433 0 : TALER_TESTING_command_incomplete (ds->is,
434 : cmd->label);
435 0 : TALER_EXCHANGE_post_aml_decision_cancel (ds->dh);
436 0 : ds->dh = NULL;
437 : }
438 3 : json_decref (ds->new_rules);
439 3 : json_decref (ds->properties);
440 3 : GNUNET_free (ds);
441 3 : }
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 4 : take_aml_decision_traits (void *cls,
456 : const void **ret,
457 : const char *trait,
458 : unsigned int index)
459 : {
460 4 : struct AmlDecisionState *ws = cls;
461 : struct TALER_TESTING_Trait traits[] = {
462 4 : TALER_TESTING_make_trait_h_normalized_payto (&ws->h_payto),
463 4 : TALER_TESTING_make_trait_aml_justification (ws->justification),
464 4 : TALER_TESTING_trait_end ()
465 : };
466 :
467 4 : return TALER_TESTING_get_trait (traits,
468 : ret,
469 : trait,
470 : index);
471 : }
472 :
473 :
474 : struct TALER_TESTING_Command
475 3 : 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 3 : ds = GNUNET_new (struct AmlDecisionState);
491 3 : ds->officer_ref_cmd = ref_officer;
492 3 : ds->account_ref_cmd = ref_operation;
493 3 : ds->keep_investigating = keep_investigating;
494 3 : ds->expiration_delay = expiration_delay;
495 3 : ds->successor_measure = successor_measure;
496 3 : ds->new_rules = json_loads (new_rules,
497 : JSON_DECODE_ANY,
498 : &err);
499 3 : if (NULL == ds->new_rules)
500 : {
501 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
502 : "Invalid JSON in new rules of %s: %s\n",
503 : label,
504 : err.text);
505 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
506 : "Input was: `%s'\n",
507 : new_rules);
508 0 : GNUNET_assert (0);
509 : }
510 3 : GNUNET_assert (NULL != ds->new_rules);
511 3 : ds->properties = json_loads (properties,
512 : 0,
513 : &err);
514 3 : if (NULL == ds->properties)
515 : {
516 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
517 : "Invalid JSON in properties of %s: %s\n",
518 : label,
519 : err.text);
520 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
521 : "Input was: `%s'\n",
522 : properties);
523 0 : GNUNET_assert (0);
524 : }
525 3 : ds->justification = justification;
526 3 : ds->expected_response = expected_response;
527 : {
528 3 : 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 3 : return cmd;
537 : }
538 : }
539 :
540 :
541 : /* end of testing_api_cmd_take_aml_decision.c */
|