exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c (13369B)
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-attributes-H_NORMALIZED_PAYTO.c 19 * @brief Implementation of the /aml/$OFFICER_PUB/attributes 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 \ 32 "taler/taler-exchange/get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h" 33 34 35 /** 36 * @brief A GET /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO Handle 37 */ 38 struct TALER_EXCHANGE_GetAmlAttributesHandle 39 { 40 41 /** 42 * Base URL of the exchange. 43 */ 44 char *base_url; 45 46 /** 47 * The url for this request. 48 */ 49 char *url; 50 51 /** 52 * Handle for the request. 53 */ 54 struct GNUNET_CURL_Job *job; 55 56 /** 57 * Function to call with the result. 58 */ 59 TALER_EXCHANGE_GetAmlAttributesCallback cb; 60 61 /** 62 * Closure for @e cb. 63 */ 64 TALER_EXCHANGE_GET_AML_ATTRIBUTES_RESULT_CLOSURE *cb_cls; 65 66 /** 67 * CURL context to use. 68 */ 69 struct GNUNET_CURL_Context *ctx; 70 71 /** 72 * Public key of the AML officer (computed from officer_priv in _create). 73 */ 74 struct TALER_AmlOfficerPublicKeyP officer_pub; 75 76 /** 77 * Private key of the AML officer (stored for signing in _start). 78 */ 79 struct TALER_AmlOfficerPrivateKeyP officer_priv; 80 81 /** 82 * Hash of the normalized payto URI for the account. 83 */ 84 struct TALER_NormalizedPaytoHashP h_payto; 85 86 /** 87 * Options set for this request. 88 */ 89 struct 90 { 91 /** 92 * Limit on the number of results (negative = before offset, positive = after). 93 * Default: -20. 94 */ 95 int64_t limit; 96 97 /** 98 * Row offset threshold. Default: UINT64_MAX. 99 */ 100 uint64_t offset; 101 } options; 102 103 }; 104 105 106 /** 107 * Parse AML attribute collection events from a JSON array. 108 * 109 * @param jdetails JSON array with AML attribute collection events 110 * @param[out] detail_ar where to write the results 111 * @return #GNUNET_OK on success 112 */ 113 static enum GNUNET_GenericReturnValue 114 parse_attributes_new ( 115 const json_t *jdetails, 116 struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent *detail_ar) 117 { 118 json_t *obj; 119 size_t idx; 120 121 json_array_foreach (jdetails, idx, obj) 122 { 123 struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent *detail 124 = &detail_ar[idx]; 125 bool by_aml_officer = false; 126 struct GNUNET_JSON_Specification spec[] = { 127 GNUNET_JSON_spec_uint64 ("rowid", 128 &detail->rowid), 129 GNUNET_JSON_spec_mark_optional ( 130 GNUNET_JSON_spec_bool ("by_aml_officer", 131 &by_aml_officer), 132 NULL), 133 GNUNET_JSON_spec_mark_optional ( 134 GNUNET_JSON_spec_object_const ("attributes", 135 &detail->attributes), 136 NULL), 137 GNUNET_JSON_spec_timestamp ("collection_time", 138 &detail->collection_time), 139 GNUNET_JSON_spec_end () 140 }; 141 142 detail->by_aml_officer = false; 143 detail->attributes = NULL; 144 if (GNUNET_OK != 145 GNUNET_JSON_parse (obj, 146 spec, 147 NULL, 148 NULL)) 149 { 150 GNUNET_break_op (0); 151 return GNUNET_SYSERR; 152 } 153 detail->by_aml_officer = by_aml_officer; 154 } 155 return GNUNET_OK; 156 } 157 158 159 /** 160 * Parse the provided data from the "200 OK" response. 161 * 162 * @param[in,out] aagh handle (callback may be zero'ed out) 163 * @param json json reply with the data 164 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 165 */ 166 static enum GNUNET_GenericReturnValue 167 parse_get_aml_attributes_ok_new ( 168 struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh, 169 const json_t *json) 170 { 171 struct TALER_EXCHANGE_GetAmlAttributesResponse lr = { 172 .hr.reply = json, 173 .hr.http_status = MHD_HTTP_OK 174 }; 175 const json_t *jdetails; 176 struct GNUNET_JSON_Specification spec[] = { 177 GNUNET_JSON_spec_array_const ("details", 178 &jdetails), 179 GNUNET_JSON_spec_end () 180 }; 181 182 if (GNUNET_OK != 183 GNUNET_JSON_parse (json, 184 spec, 185 NULL, 186 NULL)) 187 { 188 GNUNET_break_op (0); 189 return GNUNET_SYSERR; 190 } 191 lr.details.ok.details_length = json_array_size (jdetails); 192 { 193 struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent details[ 194 GNUNET_NZL (lr.details.ok.details_length)]; 195 196 memset (details, 197 0, 198 sizeof (details)); 199 lr.details.ok.details = details; 200 if (GNUNET_OK != 201 parse_attributes_new (jdetails, 202 details)) 203 { 204 GNUNET_break_op (0); 205 return GNUNET_SYSERR; 206 } 207 aagh->cb (aagh->cb_cls, 208 &lr); 209 aagh->cb = NULL; 210 } 211 return GNUNET_OK; 212 } 213 214 215 /** 216 * Function called when we're done processing the 217 * HTTP GET /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO request (new API). 218 * 219 * @param cls the `struct TALER_EXCHANGE_GetAmlAttributesHandle` 220 * @param response_code HTTP response code, 0 on error 221 * @param response parsed JSON result, NULL on error 222 */ 223 static void 224 handle_get_aml_attributes_finished (void *cls, 225 long response_code, 226 const void *response) 227 { 228 struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh = cls; 229 const json_t *j = response; 230 struct TALER_EXCHANGE_GetAmlAttributesResponse lr = { 231 .hr.reply = j, 232 .hr.http_status = (unsigned int) response_code 233 }; 234 235 aagh->job = NULL; 236 switch (response_code) 237 { 238 case 0: 239 lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 240 break; 241 case MHD_HTTP_OK: 242 if (GNUNET_OK != 243 parse_get_aml_attributes_ok_new (aagh, 244 j)) 245 { 246 GNUNET_break_op (0); 247 lr.hr.http_status = 0; 248 lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 249 break; 250 } 251 GNUNET_assert (NULL == aagh->cb); 252 TALER_EXCHANGE_get_aml_attributes_cancel (aagh); 253 return; 254 case MHD_HTTP_NO_CONTENT: 255 break; 256 case MHD_HTTP_NOT_IMPLEMENTED: 257 lr.hr.ec = TALER_JSON_get_error_code (j); 258 lr.hr.hint = TALER_JSON_get_error_hint (j); 259 break; 260 case MHD_HTTP_BAD_REQUEST: 261 lr.hr.ec = TALER_JSON_get_error_code (j); 262 lr.hr.hint = TALER_JSON_get_error_hint (j); 263 /* This should never happen, either us or the exchange is buggy 264 (or API version conflict); just pass JSON reply to the application */ 265 break; 266 case MHD_HTTP_FORBIDDEN: 267 lr.hr.ec = TALER_JSON_get_error_code (j); 268 lr.hr.hint = TALER_JSON_get_error_hint (j); 269 break; 270 case MHD_HTTP_NOT_FOUND: 271 lr.hr.ec = TALER_JSON_get_error_code (j); 272 lr.hr.hint = TALER_JSON_get_error_hint (j); 273 break; 274 case MHD_HTTP_INTERNAL_SERVER_ERROR: 275 lr.hr.ec = TALER_JSON_get_error_code (j); 276 lr.hr.hint = TALER_JSON_get_error_hint (j); 277 /* Server had an internal issue; we should retry, but this API 278 leaves this to the application */ 279 break; 280 default: 281 /* unexpected response code */ 282 GNUNET_break_op (0); 283 lr.hr.ec = TALER_JSON_get_error_code (j); 284 lr.hr.hint = TALER_JSON_get_error_hint (j); 285 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 286 "Unexpected response code %u/%d for get AML attributes\n", 287 (unsigned int) response_code, 288 (int) lr.hr.ec); 289 break; 290 } 291 if (NULL != aagh->cb) 292 aagh->cb (aagh->cb_cls, 293 &lr); 294 TALER_EXCHANGE_get_aml_attributes_cancel (aagh); 295 } 296 297 298 struct TALER_EXCHANGE_GetAmlAttributesHandle * 299 TALER_EXCHANGE_get_aml_attributes_create ( 300 struct GNUNET_CURL_Context *ctx, 301 const char *url, 302 const struct TALER_AmlOfficerPrivateKeyP *officer_priv, 303 const struct TALER_NormalizedPaytoHashP *h_payto) 304 { 305 struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh; 306 307 aagh = GNUNET_new (struct TALER_EXCHANGE_GetAmlAttributesHandle); 308 aagh->ctx = ctx; 309 aagh->base_url = GNUNET_strdup (url); 310 aagh->h_payto = *h_payto; 311 aagh->officer_priv = *officer_priv; 312 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 313 &aagh->officer_pub.eddsa_pub); 314 aagh->options.limit = -20; 315 aagh->options.offset = UINT64_MAX; 316 return aagh; 317 } 318 319 320 enum GNUNET_GenericReturnValue 321 TALER_EXCHANGE_get_aml_attributes_set_options_ ( 322 struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh, 323 unsigned int num_options, 324 const struct TALER_EXCHANGE_GetAmlAttributesOptionValue *options) 325 { 326 for (unsigned int i = 0; i < num_options; i++) 327 { 328 const struct TALER_EXCHANGE_GetAmlAttributesOptionValue *opt = &options[i]; 329 330 switch (opt->option) 331 { 332 case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_END: 333 return GNUNET_OK; 334 case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_LIMIT: 335 aagh->options.limit = opt->details.limit; 336 break; 337 case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_OFFSET: 338 aagh->options.offset = opt->details.offset; 339 break; 340 } 341 } 342 return GNUNET_OK; 343 } 344 345 346 enum TALER_ErrorCode 347 TALER_EXCHANGE_get_aml_attributes_start ( 348 struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh, 349 TALER_EXCHANGE_GetAmlAttributesCallback cb, 350 TALER_EXCHANGE_GET_AML_ATTRIBUTES_RESULT_CLOSURE *cb_cls) 351 { 352 struct TALER_AmlOfficerSignatureP officer_sig; 353 char arg_str[sizeof (aagh->officer_pub) * 2 354 + sizeof (aagh->h_payto) * 2 355 + 32]; 356 CURL *eh; 357 358 if (NULL != aagh->job) 359 { 360 GNUNET_break (0); 361 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 362 } 363 aagh->cb = cb; 364 aagh->cb_cls = cb_cls; 365 TALER_officer_aml_query_sign (&aagh->officer_priv, 366 &officer_sig); 367 { 368 char payto_s[sizeof (aagh->h_payto) * 2]; 369 char pub_str[sizeof (aagh->officer_pub) * 2]; 370 char *end; 371 372 end = GNUNET_STRINGS_data_to_string ( 373 &aagh->h_payto, 374 sizeof (aagh->h_payto), 375 payto_s, 376 sizeof (payto_s)); 377 *end = '\0'; 378 end = GNUNET_STRINGS_data_to_string ( 379 &aagh->officer_pub, 380 sizeof (aagh->officer_pub), 381 pub_str, 382 sizeof (pub_str)); 383 *end = '\0'; 384 GNUNET_snprintf (arg_str, 385 sizeof (arg_str), 386 "aml/%s/attributes/%s", 387 pub_str, 388 payto_s); 389 } 390 { 391 char limit_s[24]; 392 char offset_s[24]; 393 394 GNUNET_snprintf (limit_s, 395 sizeof (limit_s), 396 "%lld", 397 (long long) aagh->options.limit); 398 GNUNET_snprintf (offset_s, 399 sizeof (offset_s), 400 "%llu", 401 (unsigned long long) aagh->options.offset); 402 aagh->url = TALER_url_join (aagh->base_url, 403 arg_str, 404 "limit", 405 limit_s, 406 "offset", 407 offset_s, 408 NULL); 409 } 410 if (NULL == aagh->url) 411 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 412 eh = TALER_EXCHANGE_curl_easy_get_ (aagh->url); 413 if (NULL == eh) 414 { 415 GNUNET_free (aagh->url); 416 aagh->url = NULL; 417 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 418 } 419 { 420 struct curl_slist *job_headers = NULL; 421 char *hdr; 422 char sig_str[sizeof (officer_sig) * 2]; 423 char *end; 424 425 end = GNUNET_STRINGS_data_to_string ( 426 &officer_sig, 427 sizeof (officer_sig), 428 sig_str, 429 sizeof (sig_str)); 430 *end = '\0'; 431 GNUNET_asprintf (&hdr, 432 "%s: %s", 433 TALER_AML_OFFICER_SIGNATURE_HEADER, 434 sig_str); 435 job_headers = curl_slist_append (NULL, 436 hdr); 437 GNUNET_free (hdr); 438 job_headers = curl_slist_append (job_headers, 439 "Content-type: application/json"); 440 aagh->job = GNUNET_CURL_job_add2 (aagh->ctx, 441 eh, 442 job_headers, 443 &handle_get_aml_attributes_finished, 444 aagh); 445 curl_slist_free_all (job_headers); 446 } 447 if (NULL == aagh->job) 448 { 449 GNUNET_free (aagh->url); 450 aagh->url = NULL; 451 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 452 } 453 return TALER_EC_NONE; 454 } 455 456 457 void 458 TALER_EXCHANGE_get_aml_attributes_cancel ( 459 struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh) 460 { 461 if (NULL != aagh->job) 462 { 463 GNUNET_CURL_job_cancel (aagh->job); 464 aagh->job = NULL; 465 } 466 GNUNET_free (aagh->url); 467 GNUNET_free (aagh->base_url); 468 GNUNET_free (aagh); 469 } 470 471 472 /* end of exchange_api_lookup_kyc_attributes.c */