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