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