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 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_add_aml_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 "exchange_api_curl_defaults.h"
28 : #include "taler/taler_signatures.h"
29 : #include "taler/taler_curl_lib.h"
30 : #include "taler/taler_json_lib.h"
31 :
32 :
33 : struct TALER_EXCHANGE_AddAmlDecision
34 : {
35 :
36 : /**
37 : * The url for this request.
38 : */
39 : char *url;
40 :
41 : /**
42 : * Minor context that holds body and headers.
43 : */
44 : struct TALER_CURL_PostContext post_ctx;
45 :
46 : /**
47 : * Handle for the request.
48 : */
49 : struct GNUNET_CURL_Job *job;
50 :
51 : /**
52 : * Function to call with the result.
53 : */
54 : TALER_EXCHANGE_AddAmlDecisionCallback cb;
55 :
56 : /**
57 : * Closure for @a cb.
58 : */
59 : void *cb_cls;
60 :
61 : /**
62 : * Reference to the execution context.
63 : */
64 : struct GNUNET_CURL_Context *ctx;
65 : };
66 :
67 :
68 : /**
69 : * Function called when we're done processing the
70 : * HTTP POST /aml/$OFFICER_PUB/decision request.
71 : *
72 : * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *`
73 : * @param response_code HTTP response code, 0 on error
74 : * @param response response body, NULL if not in JSON
75 : */
76 : static void
77 3 : handle_add_aml_decision_finished (void *cls,
78 : long response_code,
79 : const void *response)
80 : {
81 3 : struct TALER_EXCHANGE_AddAmlDecision *wh = cls;
82 3 : const json_t *json = response;
83 3 : struct TALER_EXCHANGE_AddAmlDecisionResponse adr = {
84 3 : .hr.http_status = (unsigned int) response_code,
85 : .hr.reply = json
86 : };
87 :
88 3 : wh->job = NULL;
89 3 : switch (response_code)
90 : {
91 0 : case 0:
92 : /* no reply */
93 0 : adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
94 0 : adr.hr.hint = "server offline?";
95 0 : break;
96 2 : case MHD_HTTP_NO_CONTENT:
97 2 : break;
98 1 : case MHD_HTTP_FORBIDDEN:
99 1 : adr.hr.ec = TALER_JSON_get_error_code (json);
100 1 : adr.hr.hint = TALER_JSON_get_error_hint (json);
101 1 : break;
102 0 : case MHD_HTTP_CONFLICT:
103 0 : adr.hr.ec = TALER_JSON_get_error_code (json);
104 0 : adr.hr.hint = TALER_JSON_get_error_hint (json);
105 0 : break;
106 0 : default:
107 : /* unexpected response code */
108 0 : GNUNET_break_op (0);
109 0 : adr.hr.ec = TALER_JSON_get_error_code (json);
110 0 : adr.hr.hint = TALER_JSON_get_error_hint (json);
111 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
112 : "Unexpected response code %u/%d for exchange AML decision\n",
113 : (unsigned int) response_code,
114 : (int) adr.hr.ec);
115 0 : break;
116 : }
117 3 : if (NULL != wh->cb)
118 : {
119 3 : wh->cb (wh->cb_cls,
120 : &adr);
121 3 : wh->cb = NULL;
122 : }
123 3 : TALER_EXCHANGE_post_aml_decision_cancel (wh);
124 3 : }
125 :
126 :
127 : struct TALER_EXCHANGE_AddAmlDecision *
128 3 : TALER_EXCHANGE_post_aml_decision (
129 : struct GNUNET_CURL_Context *ctx,
130 : const char *url,
131 : const struct TALER_NormalizedPaytoHashP *h_payto,
132 : const struct TALER_FullPayto payto_uri,
133 : struct GNUNET_TIME_Timestamp decision_time,
134 : const char *successor_measure,
135 : const char *new_measures,
136 : struct GNUNET_TIME_Timestamp expiration_time,
137 : unsigned int num_rules,
138 : const struct TALER_EXCHANGE_AccountRule *rules,
139 : unsigned int num_measures,
140 : const struct TALER_EXCHANGE_MeasureInformation *measures,
141 : const json_t *properties,
142 : bool keep_investigating,
143 : const char *justification,
144 : const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
145 : unsigned int num_events,
146 : const char **events,
147 : TALER_EXCHANGE_AddAmlDecisionCallback cb,
148 : void *cb_cls)
149 : {
150 : struct TALER_AmlOfficerPublicKeyP officer_pub;
151 : struct TALER_AmlOfficerSignatureP officer_sig;
152 : struct TALER_EXCHANGE_AddAmlDecision *wh;
153 : CURL *eh;
154 : json_t *body;
155 : json_t *new_rules;
156 : json_t *jrules;
157 : json_t *jmeasures;
158 3 : json_t *jevents = NULL;
159 :
160 3 : if (0 != num_events)
161 : {
162 0 : jevents = json_array ();
163 0 : GNUNET_assert (NULL != jevents);
164 0 : for (unsigned int i = 0; i<num_events; i++)
165 0 : GNUNET_assert (0 ==
166 : json_array_append_new (jevents,
167 : json_string (events[i])));
168 : }
169 3 : jrules = json_array ();
170 3 : GNUNET_assert (NULL != jrules);
171 6 : for (unsigned int i = 0; i<num_rules; i++)
172 : {
173 3 : const struct TALER_EXCHANGE_AccountRule *al = &rules[i];
174 : json_t *rule;
175 : json_t *ameasures;
176 :
177 3 : ameasures = json_array ();
178 3 : GNUNET_assert (NULL != ameasures);
179 4 : for (unsigned int j = 0; j<al->num_measures; j++)
180 1 : GNUNET_assert (0 ==
181 : json_array_append_new (ameasures,
182 : json_string (al->measures[j])));
183 3 : rule = GNUNET_JSON_PACK (
184 : TALER_JSON_pack_kycte ("operation_type",
185 : al->operation_type),
186 : TALER_JSON_pack_amount ("threshold",
187 : &al->threshold),
188 : GNUNET_JSON_pack_time_rel ("timeframe",
189 : al->timeframe),
190 : GNUNET_JSON_pack_array_steal ("measures",
191 : ameasures),
192 : GNUNET_JSON_pack_allow_null (
193 : GNUNET_JSON_pack_array_steal ("events",
194 : jevents)),
195 : GNUNET_JSON_pack_bool ("exposed",
196 : al->exposed),
197 : GNUNET_JSON_pack_bool ("is_and_combinator",
198 : al->is_and_combinator),
199 : GNUNET_JSON_pack_uint64 ("display_priority",
200 : al->display_priority)
201 : );
202 3 : GNUNET_break (0 ==
203 : json_array_append_new (jrules,
204 : rule));
205 : }
206 :
207 3 : jmeasures = json_object ();
208 3 : GNUNET_assert (NULL != jmeasures);
209 4 : for (unsigned int i = 0; i<num_measures; i++)
210 : {
211 1 : const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i];
212 : json_t *measure;
213 :
214 1 : measure = GNUNET_JSON_PACK (
215 : GNUNET_JSON_pack_string ("check_name",
216 : mi->check_name),
217 : GNUNET_JSON_pack_allow_null (
218 : GNUNET_JSON_pack_string ("prog_name",
219 : mi->prog_name)),
220 : GNUNET_JSON_pack_allow_null (
221 : GNUNET_JSON_pack_object_incref ("context",
222 : (json_t *) mi->context))
223 : );
224 1 : GNUNET_break (0 ==
225 : json_object_set_new (jmeasures,
226 : mi->measure_name,
227 : measure));
228 : }
229 :
230 3 : new_rules = GNUNET_JSON_PACK (
231 : GNUNET_JSON_pack_timestamp ("expiration_time",
232 : expiration_time),
233 : GNUNET_JSON_pack_allow_null (
234 : GNUNET_JSON_pack_string ("successor_measure",
235 : successor_measure)),
236 : GNUNET_JSON_pack_array_steal ("rules",
237 : jrules),
238 : GNUNET_JSON_pack_object_steal ("custom_measures",
239 : jmeasures)
240 : );
241 :
242 3 : GNUNET_CRYPTO_eddsa_key_get_public (
243 : &officer_priv->eddsa_priv,
244 : &officer_pub.eddsa_pub);
245 3 : TALER_officer_aml_decision_sign (justification,
246 : decision_time,
247 : h_payto,
248 : new_rules,
249 : properties,
250 : new_measures,
251 : keep_investigating,
252 : officer_priv,
253 : &officer_sig);
254 3 : wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision);
255 3 : wh->cb = cb;
256 3 : wh->cb_cls = cb_cls;
257 3 : wh->ctx = ctx;
258 : {
259 : char *path;
260 : char opus[sizeof (officer_pub) * 2];
261 : char *end;
262 :
263 3 : end = GNUNET_STRINGS_data_to_string (
264 : &officer_pub,
265 : sizeof (officer_pub),
266 : opus,
267 : sizeof (opus));
268 3 : *end = '\0';
269 3 : GNUNET_asprintf (&path,
270 : "aml/%s/decision",
271 : opus);
272 3 : wh->url = TALER_url_join (url,
273 : path,
274 : NULL);
275 3 : GNUNET_free (path);
276 : }
277 3 : if (NULL == wh->url)
278 : {
279 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
280 : "Could not construct request URL.\n");
281 0 : GNUNET_free (wh);
282 0 : json_decref (new_rules);
283 0 : return NULL;
284 : }
285 3 : body = GNUNET_JSON_PACK (
286 : GNUNET_JSON_pack_string ("justification",
287 : justification),
288 : GNUNET_JSON_pack_data_auto ("h_payto",
289 : h_payto),
290 : GNUNET_JSON_pack_allow_null (
291 : TALER_JSON_pack_full_payto ("payto_uri",
292 : payto_uri)),
293 : GNUNET_JSON_pack_object_steal ("new_rules",
294 : new_rules),
295 : GNUNET_JSON_pack_object_incref ("properties",
296 : (json_t *) properties),
297 : GNUNET_JSON_pack_allow_null (
298 : GNUNET_JSON_pack_string ("new_measures",
299 : new_measures)),
300 : GNUNET_JSON_pack_bool ("keep_investigating",
301 : keep_investigating),
302 : GNUNET_JSON_pack_data_auto ("officer_sig",
303 : &officer_sig),
304 : GNUNET_JSON_pack_timestamp ("decision_time",
305 : decision_time));
306 3 : eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
307 6 : if ( (NULL == eh) ||
308 : (GNUNET_OK !=
309 3 : TALER_curl_easy_post (&wh->post_ctx,
310 : eh,
311 : body)) )
312 : {
313 0 : GNUNET_break (0);
314 0 : if (NULL != eh)
315 0 : curl_easy_cleanup (eh);
316 0 : json_decref (body);
317 0 : GNUNET_free (wh->url);
318 0 : return NULL;
319 : }
320 3 : json_decref (body);
321 3 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
322 : "Requesting URL '%s'\n",
323 : wh->url);
324 6 : wh->job = GNUNET_CURL_job_add2 (ctx,
325 : eh,
326 3 : wh->post_ctx.headers,
327 : &handle_add_aml_decision_finished,
328 : wh);
329 3 : if (NULL == wh->job)
330 : {
331 0 : TALER_EXCHANGE_post_aml_decision_cancel (wh);
332 0 : return NULL;
333 : }
334 3 : return wh;
335 : }
336 :
337 :
338 : void
339 3 : TALER_EXCHANGE_post_aml_decision_cancel (
340 : struct TALER_EXCHANGE_AddAmlDecision *wh)
341 : {
342 3 : if (NULL != wh->job)
343 : {
344 0 : GNUNET_CURL_job_cancel (wh->job);
345 0 : wh->job = NULL;
346 : }
347 3 : TALER_curl_easy_post_finished (&wh->post_ctx);
348 3 : GNUNET_free (wh->url);
349 3 : GNUNET_free (wh);
350 3 : }
|