Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023, 2024, 2025, 2026 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_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c
19 : * @brief Implementation of the /aml/$OFFICER_PUB/kyc-statistics/$NAME request
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/platform.h"
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler/taler_exchange_service.h"
27 : #include "taler/taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler/taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 : #include "taler/taler-exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h"
32 :
33 :
34 : /**
35 : * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAMES Handle (new API)
36 : */
37 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle
38 : {
39 :
40 : /**
41 : * The base URL of the exchange.
42 : */
43 : char *base_url;
44 :
45 : /**
46 : * The full URL for this request.
47 : */
48 : char *url;
49 :
50 : /**
51 : * Handle for the request.
52 : */
53 : struct GNUNET_CURL_Job *job;
54 :
55 : /**
56 : * Function to call with the result.
57 : */
58 : TALER_EXCHANGE_GetAmlKycStatisticsCallback cb;
59 :
60 : /**
61 : * Closure for @e cb.
62 : */
63 : TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls;
64 :
65 : /**
66 : * CURL context to use.
67 : */
68 : struct GNUNET_CURL_Context *ctx;
69 :
70 : /**
71 : * Public key of the AML officer.
72 : */
73 : struct TALER_AmlOfficerPublicKeyP officer_pub;
74 :
75 : /**
76 : * Private key of the AML officer.
77 : */
78 : struct TALER_AmlOfficerPrivateKeyP officer_priv;
79 :
80 : /**
81 : * Space-separated list of event type names to count.
82 : */
83 : char *names;
84 :
85 : /**
86 : * Options for this request.
87 : */
88 : struct
89 : {
90 : /**
91 : * Start date for statistics window. Zero means "from the beginning".
92 : */
93 : struct GNUNET_TIME_Timestamp start_date;
94 :
95 : /**
96 : * End date for statistics window. #GNUNET_TIME_UNIT_FOREVER_ABS means "up to now".
97 : */
98 : struct GNUNET_TIME_Timestamp end_date;
99 : } options;
100 :
101 : };
102 :
103 :
104 : /**
105 : * Parse the provided statistics data from the "200 OK" response.
106 : *
107 : * @param[in,out] aksh handle (callback may be zero'ed out)
108 : * @param json json reply
109 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
110 : */
111 : static enum GNUNET_GenericReturnValue
112 0 : parse_stats_ok_new (
113 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh,
114 : const json_t *json)
115 : {
116 0 : struct TALER_EXCHANGE_GetAmlKycStatisticsResponse lr = {
117 : .hr.reply = json,
118 : .hr.http_status = MHD_HTTP_OK
119 : };
120 : const json_t *jstatistics;
121 : struct GNUNET_JSON_Specification spec[] = {
122 0 : GNUNET_JSON_spec_array_const ("statistics",
123 : &jstatistics),
124 0 : GNUNET_JSON_spec_end ()
125 : };
126 :
127 0 : if (GNUNET_OK !=
128 0 : GNUNET_JSON_parse (json,
129 : spec,
130 : NULL,
131 : NULL))
132 : {
133 0 : GNUNET_break_op (0);
134 0 : return GNUNET_SYSERR;
135 : }
136 0 : lr.details.ok.statistics_length = json_array_size (jstatistics);
137 0 : {
138 0 : struct TALER_EXCHANGE_GetAmlKycStatisticsEventCounter statistics[
139 0 : GNUNET_NZL (lr.details.ok.statistics_length)];
140 : json_t *obj;
141 : size_t idx;
142 :
143 0 : memset (statistics,
144 : 0,
145 : sizeof (statistics));
146 0 : lr.details.ok.statistics = statistics;
147 0 : json_array_foreach (jstatistics, idx, obj)
148 : {
149 0 : struct TALER_EXCHANGE_GetAmlKycStatisticsEventCounter *ec
150 : = &statistics[idx];
151 : struct GNUNET_JSON_Specification ispec[] = {
152 0 : GNUNET_JSON_spec_string ("event",
153 : &ec->name),
154 0 : GNUNET_JSON_spec_uint64 ("counter",
155 : &ec->counter),
156 0 : GNUNET_JSON_spec_end ()
157 : };
158 :
159 0 : if (GNUNET_OK !=
160 0 : GNUNET_JSON_parse (obj,
161 : ispec,
162 : NULL,
163 : NULL))
164 : {
165 0 : GNUNET_break_op (0);
166 0 : return GNUNET_SYSERR;
167 : }
168 : }
169 0 : aksh->cb (aksh->cb_cls,
170 : &lr);
171 0 : aksh->cb = NULL;
172 : }
173 0 : return GNUNET_OK;
174 : }
175 :
176 :
177 : /**
178 : * Function called when we're done processing the
179 : * HTTP GET /aml/$OFFICER_PUB/kyc-statistics/$NAMES request.
180 : *
181 : * @param cls the `struct TALER_EXCHANGE_GetAmlKycStatisticsHandle`
182 : * @param response_code HTTP response code, 0 on error
183 : * @param response parsed JSON result, NULL on error
184 : */
185 : static void
186 0 : handle_get_aml_kyc_statistics_finished (void *cls,
187 : long response_code,
188 : const void *response)
189 : {
190 0 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh = cls;
191 0 : const json_t *j = response;
192 0 : struct TALER_EXCHANGE_GetAmlKycStatisticsResponse lr = {
193 : .hr.reply = j,
194 0 : .hr.http_status = (unsigned int) response_code
195 : };
196 :
197 0 : aksh->job = NULL;
198 0 : switch (response_code)
199 : {
200 0 : case 0:
201 0 : lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
202 0 : break;
203 0 : case MHD_HTTP_OK:
204 0 : if (GNUNET_OK !=
205 0 : parse_stats_ok_new (aksh,
206 : j))
207 : {
208 0 : GNUNET_break_op (0);
209 0 : lr.hr.http_status = 0;
210 0 : lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
211 0 : break;
212 : }
213 0 : GNUNET_assert (NULL == aksh->cb);
214 0 : TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh);
215 0 : return;
216 0 : case MHD_HTTP_NO_CONTENT:
217 0 : break;
218 0 : case MHD_HTTP_BAD_REQUEST:
219 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
220 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
221 : /* This should never happen, either us or the exchange is buggy
222 : (or API version conflict); just pass JSON reply to the application */
223 0 : break;
224 0 : case MHD_HTTP_FORBIDDEN:
225 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
226 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
227 0 : break;
228 0 : case MHD_HTTP_NOT_FOUND:
229 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
230 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
231 0 : break;
232 0 : case MHD_HTTP_URI_TOO_LONG:
233 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
234 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
235 0 : break;
236 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
237 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
238 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
239 : /* Server had an internal issue; we should retry, but this API
240 : leaves this to the application */
241 0 : break;
242 0 : default:
243 : /* unexpected response code */
244 0 : GNUNET_break_op (0);
245 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
246 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
247 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
248 : "Unexpected response code %u/%d for GET KYC statistics\n",
249 : (unsigned int) response_code,
250 : (int) lr.hr.ec);
251 0 : break;
252 : }
253 0 : if (NULL != aksh->cb)
254 0 : aksh->cb (aksh->cb_cls,
255 : &lr);
256 0 : TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh);
257 : }
258 :
259 :
260 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *
261 0 : TALER_EXCHANGE_get_aml_kyc_statistics_create (
262 : struct GNUNET_CURL_Context *ctx,
263 : const char *url,
264 : const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
265 : const char *names)
266 : {
267 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh;
268 :
269 0 : aksh = GNUNET_new (struct TALER_EXCHANGE_GetAmlKycStatisticsHandle);
270 0 : aksh->ctx = ctx;
271 0 : aksh->base_url = GNUNET_strdup (url);
272 0 : aksh->officer_priv = *officer_priv;
273 0 : GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
274 : &aksh->officer_pub.eddsa_pub);
275 0 : aksh->names = GNUNET_strdup (names);
276 : /* Default: no start date filter, no end date filter */
277 0 : aksh->options.start_date = GNUNET_TIME_UNIT_ZERO_TS;
278 0 : aksh->options.end_date = GNUNET_TIME_UNIT_FOREVER_TS;
279 0 : return aksh;
280 : }
281 :
282 :
283 : enum GNUNET_GenericReturnValue
284 0 : TALER_EXCHANGE_get_aml_kyc_statistics_set_options_ (
285 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh,
286 : unsigned int num_options,
287 : const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *options)
288 : {
289 0 : for (unsigned int i = 0; i < num_options; i++)
290 : {
291 0 : const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *opt
292 0 : = &options[i];
293 :
294 0 : switch (opt->option)
295 : {
296 0 : case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END:
297 0 : return GNUNET_OK;
298 0 : case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_START_DATE:
299 0 : aksh->options.start_date = opt->details.start_date;
300 0 : break;
301 0 : case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END_DATE:
302 0 : aksh->options.end_date = opt->details.end_date;
303 0 : break;
304 : }
305 : }
306 0 : return GNUNET_OK;
307 : }
308 :
309 :
310 : enum TALER_ErrorCode
311 0 : TALER_EXCHANGE_get_aml_kyc_statistics_start (
312 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh,
313 : TALER_EXCHANGE_GetAmlKycStatisticsCallback cb,
314 : TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls)
315 : {
316 : struct TALER_AmlOfficerSignatureP officer_sig;
317 : CURL *eh;
318 : char sd_str[32];
319 : char ed_str[32];
320 0 : const char *sd = NULL;
321 0 : const char *ed = NULL;
322 :
323 0 : if (NULL != aksh->job)
324 : {
325 0 : GNUNET_break (0);
326 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
327 : }
328 0 : aksh->cb = cb;
329 0 : aksh->cb_cls = cb_cls;
330 0 : TALER_officer_aml_query_sign (&aksh->officer_priv,
331 : &officer_sig);
332 0 : if (! GNUNET_TIME_absolute_is_zero (aksh->options.start_date.abs_time))
333 : {
334 : unsigned long long sec;
335 :
336 0 : sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (
337 : aksh->options.start_date);
338 0 : GNUNET_snprintf (sd_str,
339 : sizeof (sd_str),
340 : "%llu",
341 : sec);
342 0 : sd = sd_str;
343 : }
344 0 : if (! GNUNET_TIME_absolute_is_never (aksh->options.end_date.abs_time))
345 : {
346 : unsigned long long sec;
347 :
348 0 : sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (
349 : aksh->options.end_date);
350 0 : GNUNET_snprintf (ed_str,
351 : sizeof (ed_str),
352 : "%llu",
353 : sec);
354 0 : ed = ed_str;
355 : }
356 : {
357 : char pub_str[sizeof (aksh->officer_pub) * 2];
358 : char arg_str[sizeof (aksh->officer_pub) * 2 + 32];
359 : char *end;
360 :
361 0 : end = GNUNET_STRINGS_data_to_string (
362 0 : &aksh->officer_pub,
363 : sizeof (aksh->officer_pub),
364 : pub_str,
365 : sizeof (pub_str));
366 0 : *end = '\0';
367 0 : GNUNET_snprintf (arg_str,
368 : sizeof (arg_str),
369 : "aml/%s/kyc-statistics/%s",
370 : pub_str,
371 : aksh->names);
372 0 : aksh->url = TALER_url_join (aksh->base_url,
373 : arg_str,
374 : "start_date",
375 : sd,
376 : "end_date",
377 : ed,
378 : NULL);
379 : }
380 0 : if (NULL == aksh->url)
381 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
382 0 : eh = TALER_EXCHANGE_curl_easy_get_ (aksh->url);
383 0 : if (NULL == eh)
384 : {
385 0 : GNUNET_free (aksh->url);
386 0 : aksh->url = NULL;
387 0 : return TALER_EC_GENERIC_ALLOCATION_FAILURE;
388 : }
389 : {
390 0 : struct curl_slist *job_headers = NULL;
391 : char *hdr;
392 : char sig_str[sizeof (officer_sig) * 2];
393 : char *end;
394 :
395 0 : end = GNUNET_STRINGS_data_to_string (
396 : &officer_sig,
397 : sizeof (officer_sig),
398 : sig_str,
399 : sizeof (sig_str));
400 0 : *end = '\0';
401 0 : GNUNET_asprintf (&hdr,
402 : "%s: %s",
403 : TALER_AML_OFFICER_SIGNATURE_HEADER,
404 : sig_str);
405 0 : job_headers = curl_slist_append (NULL,
406 : hdr);
407 0 : GNUNET_free (hdr);
408 0 : aksh->job = GNUNET_CURL_job_add2 (aksh->ctx,
409 : eh,
410 : job_headers,
411 : &handle_get_aml_kyc_statistics_finished,
412 : aksh);
413 0 : curl_slist_free_all (job_headers);
414 : }
415 0 : if (NULL == aksh->job)
416 : {
417 0 : GNUNET_free (aksh->url);
418 0 : aksh->url = NULL;
419 0 : return TALER_EC_GENERIC_ALLOCATION_FAILURE;
420 : }
421 0 : return TALER_EC_NONE;
422 : }
423 :
424 :
425 : void
426 0 : TALER_EXCHANGE_get_aml_kyc_statistics_cancel (
427 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh)
428 : {
429 0 : if (NULL != aksh->job)
430 : {
431 0 : GNUNET_CURL_job_cancel (aksh->job);
432 0 : aksh->job = NULL;
433 : }
434 0 : GNUNET_free (aksh->url);
435 0 : GNUNET_free (aksh->base_url);
436 0 : GNUNET_free (aksh->names);
437 0 : GNUNET_free (aksh);
438 0 : }
439 :
440 :
441 : /* end of exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c */
|