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 ("name",
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_INTERNAL_SERVER_ERROR:
229 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
230 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
231 : /* Server had an internal issue; we should retry, but this API
232 : leaves this to the application */
233 0 : break;
234 0 : default:
235 : /* unexpected response code */
236 0 : GNUNET_break_op (0);
237 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
238 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
239 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
240 : "Unexpected response code %u/%d for GET KYC statistics\n",
241 : (unsigned int) response_code,
242 : (int) lr.hr.ec);
243 0 : break;
244 : }
245 0 : if (NULL != aksh->cb)
246 0 : aksh->cb (aksh->cb_cls,
247 : &lr);
248 0 : TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh);
249 : }
250 :
251 :
252 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *
253 0 : TALER_EXCHANGE_get_aml_kyc_statistics_create (
254 : struct GNUNET_CURL_Context *ctx,
255 : const char *url,
256 : const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
257 : const char *names)
258 : {
259 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh;
260 :
261 0 : aksh = GNUNET_new (struct TALER_EXCHANGE_GetAmlKycStatisticsHandle);
262 0 : aksh->ctx = ctx;
263 0 : aksh->base_url = GNUNET_strdup (url);
264 0 : aksh->officer_priv = *officer_priv;
265 0 : GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
266 : &aksh->officer_pub.eddsa_pub);
267 0 : aksh->names = GNUNET_strdup (names);
268 : /* Default: no start date filter, no end date filter */
269 0 : aksh->options.start_date = GNUNET_TIME_UNIT_ZERO_TS;
270 0 : aksh->options.end_date = GNUNET_TIME_UNIT_FOREVER_TS;
271 0 : return aksh;
272 : }
273 :
274 :
275 : enum GNUNET_GenericReturnValue
276 0 : TALER_EXCHANGE_get_aml_kyc_statistics_set_options_ (
277 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh,
278 : unsigned int num_options,
279 : const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *options)
280 : {
281 0 : for (unsigned int i = 0; i < num_options; i++)
282 : {
283 0 : const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *opt
284 0 : = &options[i];
285 :
286 0 : switch (opt->option)
287 : {
288 0 : case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END:
289 0 : return GNUNET_OK;
290 0 : case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_START_DATE:
291 0 : aksh->options.start_date = opt->details.start_date;
292 0 : break;
293 0 : case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END_DATE:
294 0 : aksh->options.end_date = opt->details.end_date;
295 0 : break;
296 : }
297 : }
298 0 : return GNUNET_OK;
299 : }
300 :
301 :
302 : enum TALER_ErrorCode
303 0 : TALER_EXCHANGE_get_aml_kyc_statistics_start (
304 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh,
305 : TALER_EXCHANGE_GetAmlKycStatisticsCallback cb,
306 : TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls)
307 : {
308 : struct TALER_AmlOfficerSignatureP officer_sig;
309 : CURL *eh;
310 : char sd_str[32];
311 : char ed_str[32];
312 0 : const char *sd = NULL;
313 0 : const char *ed = NULL;
314 :
315 0 : if (NULL != aksh->job)
316 : {
317 0 : GNUNET_break (0);
318 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
319 : }
320 0 : aksh->cb = cb;
321 0 : aksh->cb_cls = cb_cls;
322 0 : TALER_officer_aml_query_sign (&aksh->officer_priv,
323 : &officer_sig);
324 0 : if (! GNUNET_TIME_absolute_is_zero (aksh->options.start_date.abs_time))
325 : {
326 : unsigned long long sec;
327 :
328 0 : sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (
329 : aksh->options.start_date);
330 0 : GNUNET_snprintf (sd_str,
331 : sizeof (sd_str),
332 : "%llu",
333 : sec);
334 0 : sd = sd_str;
335 : }
336 0 : if (! GNUNET_TIME_absolute_is_never (aksh->options.end_date.abs_time))
337 : {
338 : unsigned long long sec;
339 :
340 0 : sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (
341 : aksh->options.end_date);
342 0 : GNUNET_snprintf (ed_str,
343 : sizeof (ed_str),
344 : "%llu",
345 : sec);
346 0 : ed = ed_str;
347 : }
348 : {
349 : char pub_str[sizeof (aksh->officer_pub) * 2];
350 : char arg_str[sizeof (aksh->officer_pub) * 2 + 32];
351 : char *end;
352 :
353 0 : end = GNUNET_STRINGS_data_to_string (
354 0 : &aksh->officer_pub,
355 : sizeof (aksh->officer_pub),
356 : pub_str,
357 : sizeof (pub_str));
358 0 : *end = '\0';
359 0 : GNUNET_snprintf (arg_str,
360 : sizeof (arg_str),
361 : "aml/%s/kyc-statistics/%s",
362 : pub_str,
363 : aksh->names);
364 0 : aksh->url = TALER_url_join (aksh->base_url,
365 : arg_str,
366 : "start_date",
367 : sd,
368 : "end_date",
369 : ed,
370 : NULL);
371 : }
372 0 : if (NULL == aksh->url)
373 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
374 0 : eh = TALER_EXCHANGE_curl_easy_get_ (aksh->url);
375 0 : if (NULL == eh)
376 : {
377 0 : GNUNET_free (aksh->url);
378 0 : aksh->url = NULL;
379 0 : return TALER_EC_GENERIC_ALLOCATION_FAILURE;
380 : }
381 : {
382 0 : struct curl_slist *job_headers = NULL;
383 : char *hdr;
384 : char sig_str[sizeof (officer_sig) * 2];
385 : char *end;
386 :
387 0 : end = GNUNET_STRINGS_data_to_string (
388 : &officer_sig,
389 : sizeof (officer_sig),
390 : sig_str,
391 : sizeof (sig_str));
392 0 : *end = '\0';
393 0 : GNUNET_asprintf (&hdr,
394 : "%s: %s",
395 : TALER_AML_OFFICER_SIGNATURE_HEADER,
396 : sig_str);
397 0 : job_headers = curl_slist_append (NULL,
398 : hdr);
399 0 : GNUNET_free (hdr);
400 0 : job_headers = curl_slist_append (job_headers,
401 : "Content-type: application/json");
402 0 : aksh->job = GNUNET_CURL_job_add2 (aksh->ctx,
403 : eh,
404 : job_headers,
405 : &handle_get_aml_kyc_statistics_finished,
406 : aksh);
407 0 : curl_slist_free_all (job_headers);
408 : }
409 0 : if (NULL == aksh->job)
410 : {
411 0 : GNUNET_free (aksh->url);
412 0 : aksh->url = NULL;
413 0 : return TALER_EC_GENERIC_ALLOCATION_FAILURE;
414 : }
415 0 : return TALER_EC_NONE;
416 : }
417 :
418 :
419 : void
420 0 : TALER_EXCHANGE_get_aml_kyc_statistics_cancel (
421 : struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh)
422 : {
423 0 : if (NULL != aksh->job)
424 : {
425 0 : GNUNET_CURL_job_cancel (aksh->job);
426 0 : aksh->job = NULL;
427 : }
428 0 : GNUNET_free (aksh->url);
429 0 : GNUNET_free (aksh->base_url);
430 0 : GNUNET_free (aksh->names);
431 0 : GNUNET_free (aksh);
432 0 : }
433 :
434 :
435 : /* end of exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c */
|