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