merchant_api_get-private-kyc.c (17455B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023-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 Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, 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 Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file src/lib/merchant_api_get-private-kyc.c 19 * @brief Implementation of the GET /private/kyc request 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include <curl/curl.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include <taler/merchant/get-private-kyc.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum length of the KYC arrays supported. 35 */ 36 #define MAX_KYC 1024 37 38 39 /** 40 * Handle for a GET /private/kyc operation. 41 */ 42 struct TALER_MERCHANT_GetPrivateKycHandle 43 { 44 /** 45 * Base URL of the merchant backend. 46 */ 47 char *base_url; 48 49 /** 50 * The full URL for this request. 51 */ 52 char *url; 53 54 /** 55 * Handle for the request. 56 */ 57 struct GNUNET_CURL_Job *job; 58 59 /** 60 * Function to call with the result. 61 */ 62 TALER_MERCHANT_GetPrivateKycCallback cb; 63 64 /** 65 * Closure for @a cb. 66 */ 67 TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls; 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 74 /** 75 * Hash of the wire account to filter by, or NULL. 76 */ 77 const struct TALER_MerchantWireHashP *h_wire; 78 79 /** 80 * Storage for the h_wire value (if set). 81 */ 82 struct TALER_MerchantWireHashP h_wire_val; 83 84 /** 85 * True if @e h_wire was set. 86 */ 87 bool have_h_wire; 88 89 /** 90 * Exchange URL filter, or NULL. 91 */ 92 char *exchange_url; 93 94 /** 95 * Long-poll target. 96 */ 97 enum TALER_EXCHANGE_KycLongPollTarget lpt; 98 99 /** 100 * Long polling timeout. 101 */ 102 struct GNUNET_TIME_Relative timeout; 103 104 /** 105 * Instance ID for management mode, or NULL. 106 */ 107 char *instance_id; 108 109 /** 110 * Long-poll status filter, or NULL. 111 */ 112 char *lp_status; 113 114 /** 115 * Long-poll negated status filter, or NULL. 116 */ 117 char *lp_not_status; 118 119 /** 120 * Long-poll ETag to suppress unchanged responses. 121 */ 122 struct GNUNET_ShortHashCode lp_not_etag; 123 124 /** 125 * True if @e lp_not_etag was set. 126 */ 127 bool have_lp_not_etag; 128 }; 129 130 131 /** 132 * Parse @a jkyc response and call the continuation on success. 133 * 134 * @param kyc operation handle 135 * @param[in,out] kr response details 136 * @param jkyc array from the reply 137 * @return #GNUNET_OK on success (callback was called) 138 */ 139 static enum GNUNET_GenericReturnValue 140 parse_kyc (struct TALER_MERCHANT_GetPrivateKycHandle *kyc, 141 struct TALER_MERCHANT_GetPrivateKycResponse *kr, 142 const json_t *jkyc) 143 { 144 unsigned int num_kycs = (unsigned int) json_array_size (jkyc); 145 unsigned int num_limits = 0; 146 unsigned int num_kycauths = 0; 147 unsigned int pos_limits = 0; 148 unsigned int pos_kycauths = 0; 149 150 if ( (json_array_size (jkyc) != (size_t) num_kycs) || 151 (num_kycs > MAX_KYC) ) 152 { 153 GNUNET_break_op (0); 154 return GNUNET_SYSERR; 155 } 156 157 for (unsigned int i = 0; i<num_kycs; i++) 158 { 159 const json_t *jlimits = NULL; 160 const json_t *jkycauths = NULL; 161 struct GNUNET_JSON_Specification spec[] = { 162 GNUNET_JSON_spec_mark_optional ( 163 GNUNET_JSON_spec_array_const ( 164 "limits", 165 &jlimits), 166 NULL), 167 GNUNET_JSON_spec_mark_optional ( 168 GNUNET_JSON_spec_array_const ( 169 "payto_kycauths", 170 &jkycauths), 171 NULL), 172 GNUNET_JSON_spec_end () 173 }; 174 175 if (GNUNET_OK != 176 GNUNET_JSON_parse (json_array_get (jkyc, 177 i), 178 spec, 179 NULL, NULL)) 180 { 181 GNUNET_break_op (0); 182 return GNUNET_SYSERR; 183 } 184 num_limits += json_array_size (jlimits); 185 num_kycauths += json_array_size (jkycauths); 186 if ( (num_limits > MAX_KYC) || 187 (num_kycauths > MAX_KYC) ) 188 { 189 /* Bound the stack VLAs declared below by an untrusted response. */ 190 GNUNET_break_op (0); 191 return GNUNET_SYSERR; 192 } 193 } 194 195 { 196 struct TALER_MERCHANT_GetPrivateKycRedirectDetail kycs[ 197 GNUNET_NZL (num_kycs)]; 198 struct TALER_EXCHANGE_AccountLimit limits[ 199 GNUNET_NZL (num_limits)]; 200 struct TALER_FullPayto payto_kycauths[ 201 GNUNET_NZL (num_kycauths)]; 202 203 memset (kycs, 204 0, 205 sizeof (kycs)); 206 for (unsigned int i = 0; i<num_kycs; i++) 207 { 208 struct TALER_MERCHANT_GetPrivateKycRedirectDetail *rd 209 = &kycs[i]; 210 const json_t *jlimits = NULL; 211 const json_t *jkycauths = NULL; 212 uint32_t hs; 213 struct GNUNET_JSON_Specification spec[] = { 214 TALER_JSON_spec_full_payto_uri ( 215 "payto_uri", 216 &rd->payto_uri), 217 TALER_JSON_spec_web_url ( 218 "exchange_url", 219 &rd->exchange_url), 220 GNUNET_JSON_spec_uint32 ( 221 "exchange_http_status", 222 &hs), 223 GNUNET_JSON_spec_bool ( 224 "no_keys", 225 &rd->no_keys), 226 GNUNET_JSON_spec_bool ( 227 "auth_conflict", 228 &rd->auth_conflict), 229 GNUNET_JSON_spec_mark_optional ( 230 TALER_JSON_spec_ec ( 231 "exchange_code", 232 &rd->exchange_code), 233 NULL), 234 GNUNET_JSON_spec_mark_optional ( 235 GNUNET_JSON_spec_fixed_auto ( 236 "access_token", 237 &rd->access_token), 238 &rd->no_access_token), 239 GNUNET_JSON_spec_fixed_auto ( 240 "h_wire", 241 &rd->h_wire), 242 GNUNET_JSON_spec_mark_optional ( 243 GNUNET_JSON_spec_string ( 244 "status", 245 &rd->status), 246 NULL), 247 /* Mandatory since **v25** */ 248 GNUNET_JSON_spec_mark_optional ( 249 GNUNET_JSON_spec_string ( 250 "exchange_currency", 251 &rd->exchange_currency), 252 NULL), 253 GNUNET_JSON_spec_mark_optional ( 254 GNUNET_JSON_spec_array_const ( 255 "limits", 256 &jlimits), 257 NULL), 258 GNUNET_JSON_spec_mark_optional ( 259 GNUNET_JSON_spec_array_const ( 260 "payto_kycauths", 261 &jkycauths), 262 NULL), 263 GNUNET_JSON_spec_end () 264 }; 265 size_t j; 266 json_t *jlimit; 267 json_t *jkycauth; 268 269 if (GNUNET_OK != 270 GNUNET_JSON_parse (json_array_get (jkyc, 271 i), 272 spec, 273 NULL, NULL)) 274 { 275 GNUNET_break_op (0); 276 return GNUNET_SYSERR; 277 } 278 rd->exchange_http_status = (unsigned int) hs; 279 rd->limits = &limits[pos_limits]; 280 rd->limits_length = json_array_size (jlimits); 281 json_array_foreach (jlimits, j, jlimit) 282 { 283 struct TALER_EXCHANGE_AccountLimit *limit 284 = &limits[pos_limits]; 285 struct GNUNET_JSON_Specification jspec[] = { 286 TALER_JSON_spec_kycte ( 287 "operation_type", 288 &limit->operation_type), 289 GNUNET_JSON_spec_relative_time ( 290 "timeframe", 291 &limit->timeframe), 292 TALER_JSON_spec_amount_any ( 293 "threshold", 294 &limit->threshold), 295 GNUNET_JSON_spec_mark_optional ( 296 GNUNET_JSON_spec_bool ( 297 "soft_limit", 298 &limit->soft_limit), 299 NULL), 300 GNUNET_JSON_spec_end () 301 }; 302 303 GNUNET_assert (pos_limits < num_limits); 304 limit->soft_limit = false; 305 if (GNUNET_OK != 306 GNUNET_JSON_parse (jlimit, 307 jspec, 308 NULL, NULL)) 309 { 310 GNUNET_break_op (0); 311 return GNUNET_SYSERR; 312 } 313 pos_limits++; 314 } 315 rd->payto_kycauths = &payto_kycauths[pos_kycauths]; 316 rd->pkycauth_length = json_array_size (jkycauths); 317 json_array_foreach (jkycauths, j, jkycauth) 318 { 319 GNUNET_assert (pos_kycauths < num_kycauths); 320 payto_kycauths[pos_kycauths].full_payto 321 = (char *) json_string_value (jkycauth); 322 if (NULL == payto_kycauths[pos_kycauths].full_payto) 323 { 324 GNUNET_break_op (0); 325 return GNUNET_SYSERR; 326 } 327 pos_kycauths++; 328 } 329 } 330 kr->details.ok.kycs = kycs; 331 kr->details.ok.kycs_length = num_kycs; 332 kyc->cb (kyc->cb_cls, 333 kr); 334 kyc->cb = NULL; 335 } 336 return GNUNET_OK; 337 } 338 339 340 /** 341 * Function called when we're done processing the 342 * HTTP GET /private/kyc request. 343 * 344 * @param cls the `struct TALER_MERCHANT_GetPrivateKycHandle` 345 * @param response_code HTTP response code, 0 on error 346 * @param response response body, NULL if not in JSON 347 */ 348 static void 349 handle_get_kyc_finished (void *cls, 350 long response_code, 351 const void *response) 352 { 353 struct TALER_MERCHANT_GetPrivateKycHandle *kyc = cls; 354 const json_t *json = response; 355 struct TALER_MERCHANT_GetPrivateKycResponse kr = { 356 .hr.http_status = (unsigned int) response_code, 357 .hr.reply = json 358 }; 359 360 kyc->job = NULL; 361 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 362 "Got /private/kyc response with status code %u\n", 363 (unsigned int) response_code); 364 switch (response_code) 365 { 366 case MHD_HTTP_OK: 367 { 368 const json_t *jkyc; 369 struct GNUNET_JSON_Specification spec[] = { 370 GNUNET_JSON_spec_array_const ("kyc_data", 371 &jkyc), 372 GNUNET_JSON_spec_end () 373 }; 374 375 if (GNUNET_OK != 376 GNUNET_JSON_parse (json, 377 spec, 378 NULL, NULL)) 379 { 380 kr.hr.http_status = 0; 381 kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 382 break; 383 } 384 if (GNUNET_OK != 385 parse_kyc (kyc, 386 &kr, 387 jkyc)) 388 { 389 kr.hr.http_status = 0; 390 kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 391 break; 392 } 393 /* parse_kyc called the continuation already */ 394 TALER_MERCHANT_get_private_kyc_cancel (kyc); 395 return; 396 } 397 case MHD_HTTP_NO_CONTENT: 398 break; 399 case MHD_HTTP_NOT_MODIFIED: 400 /* ETag matched; nothing changed. No body expected. */ 401 break; 402 case MHD_HTTP_BAD_REQUEST: 403 kr.hr.ec = TALER_JSON_get_error_code (json); 404 kr.hr.hint = TALER_JSON_get_error_hint (json); 405 break; 406 case MHD_HTTP_UNAUTHORIZED: 407 kr.hr.ec = TALER_JSON_get_error_code (json); 408 kr.hr.hint = TALER_JSON_get_error_hint (json); 409 break; 410 case MHD_HTTP_NOT_FOUND: 411 kr.hr.ec = TALER_JSON_get_error_code (json); 412 kr.hr.hint = TALER_JSON_get_error_hint (json); 413 break; 414 case MHD_HTTP_NOT_ACCEPTABLE: 415 kr.hr.ec = TALER_JSON_get_error_code (json); 416 kr.hr.hint = TALER_JSON_get_error_hint (json); 417 break; 418 case MHD_HTTP_SERVICE_UNAVAILABLE: 419 break; 420 default: 421 kr.hr.ec = TALER_JSON_get_error_code (json); 422 kr.hr.hint = TALER_JSON_get_error_hint (json); 423 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 424 "Unexpected response code %u/%d\n", 425 (unsigned int) response_code, 426 (int) kr.hr.ec); 427 break; 428 } 429 kyc->cb (kyc->cb_cls, 430 &kr); 431 TALER_MERCHANT_get_private_kyc_cancel (kyc); 432 } 433 434 435 struct TALER_MERCHANT_GetPrivateKycHandle * 436 TALER_MERCHANT_get_private_kyc_create ( 437 struct GNUNET_CURL_Context *ctx, 438 const char *url) 439 { 440 struct TALER_MERCHANT_GetPrivateKycHandle *kyc; 441 442 kyc = GNUNET_new (struct TALER_MERCHANT_GetPrivateKycHandle); 443 kyc->ctx = ctx; 444 kyc->base_url = GNUNET_strdup (url); 445 kyc->lpt = TALER_EXCHANGE_KLPT_NONE; 446 return kyc; 447 } 448 449 450 enum GNUNET_GenericReturnValue 451 TALER_MERCHANT_get_private_kyc_set_options_ ( 452 struct TALER_MERCHANT_GetPrivateKycHandle *kyc, 453 unsigned int num_options, 454 const struct TALER_MERCHANT_GetPrivateKycOptionValue *options) 455 { 456 for (unsigned int i = 0; i < num_options; i++) 457 { 458 const struct TALER_MERCHANT_GetPrivateKycOptionValue *opt = 459 &options[i]; 460 461 switch (opt->option) 462 { 463 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_END: 464 return GNUNET_OK; 465 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_H_WIRE: 466 if (NULL != opt->details.h_wire) 467 { 468 kyc->h_wire_val = *opt->details.h_wire; 469 kyc->h_wire = &kyc->h_wire_val; 470 kyc->have_h_wire = true; 471 } 472 break; 473 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_EXCHANGE_URL: 474 GNUNET_free (kyc->exchange_url); 475 if (NULL != opt->details.exchange_url) 476 kyc->exchange_url = GNUNET_strdup (opt->details.exchange_url); 477 break; 478 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LPT: 479 kyc->lpt = opt->details.lpt; 480 break; 481 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_TIMEOUT: 482 kyc->timeout = opt->details.timeout; 483 break; 484 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_INSTANCE_ID: 485 GNUNET_free (kyc->instance_id); 486 if (NULL != opt->details.instance_id) 487 kyc->instance_id = GNUNET_strdup (opt->details.instance_id); 488 break; 489 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_STATUS: 490 GNUNET_free (kyc->lp_status); 491 if (NULL != opt->details.lp_status) 492 kyc->lp_status = GNUNET_strdup (opt->details.lp_status); 493 break; 494 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_STATUS: 495 GNUNET_free (kyc->lp_not_status); 496 if (NULL != opt->details.lp_not_status) 497 kyc->lp_not_status = GNUNET_strdup (opt->details.lp_not_status); 498 break; 499 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_ETAG: 500 if (NULL != opt->details.lp_not_etag) 501 { 502 kyc->lp_not_etag = *opt->details.lp_not_etag; 503 kyc->have_lp_not_etag = true; 504 } 505 break; 506 default: 507 GNUNET_break (0); 508 return GNUNET_NO; 509 } 510 } 511 return GNUNET_OK; 512 } 513 514 515 enum TALER_ErrorCode 516 TALER_MERCHANT_get_private_kyc_start ( 517 struct TALER_MERCHANT_GetPrivateKycHandle *kyc, 518 TALER_MERCHANT_GetPrivateKycCallback cb, 519 TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls) 520 { 521 CURL *eh; 522 unsigned long long tms; 523 char timeout_ms[32]; 524 char lpt_str[32]; 525 char *base_path; 526 527 kyc->cb = cb; 528 kyc->cb_cls = cb_cls; 529 530 /* Build the base path depending on whether instance_id is set */ 531 if (NULL != kyc->instance_id) 532 { 533 GNUNET_asprintf (&base_path, 534 "%smanagement/instances/%s/", 535 kyc->base_url, 536 kyc->instance_id); 537 } 538 else 539 { 540 GNUNET_asprintf (&base_path, 541 "%sprivate/", 542 kyc->base_url); 543 } 544 545 GNUNET_snprintf (lpt_str, 546 sizeof (lpt_str), 547 "%d", 548 (int) kyc->lpt); 549 tms = kyc->timeout.rel_value_us 550 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 551 GNUNET_snprintf (timeout_ms, 552 sizeof (timeout_ms), 553 "%llu", 554 tms); 555 { 556 char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1]; 557 558 if (kyc->have_lp_not_etag) 559 { 560 char *end; 561 562 end = GNUNET_STRINGS_data_to_string ( 563 &kyc->lp_not_etag, 564 sizeof (kyc->lp_not_etag), 565 etag_str, 566 sizeof (etag_str) - 1); 567 *end = '\0'; 568 } 569 kyc->url 570 = TALER_url_join ( 571 base_path, 572 "kyc", 573 "h_wire", 574 kyc->have_h_wire 575 ? GNUNET_h2s_full (&kyc->h_wire_val.hash) 576 : NULL, 577 "exchange_url", 578 kyc->exchange_url, 579 "timeout_ms", 580 GNUNET_TIME_relative_is_zero (kyc->timeout) 581 ? NULL 582 : timeout_ms, 583 "lpt", 584 TALER_EXCHANGE_KLPT_NONE == kyc->lpt 585 ? NULL 586 : lpt_str, 587 "lp_status", 588 kyc->lp_status, 589 "lp_not_status", 590 kyc->lp_not_status, 591 "lp_not_etag", 592 kyc->have_lp_not_etag 593 ? etag_str 594 : NULL, 595 NULL); 596 } 597 GNUNET_free (base_path); 598 if (NULL == kyc->url) 599 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 600 eh = TALER_MERCHANT_curl_easy_get_ (kyc->url); 601 if (NULL == eh) 602 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 603 if (0 != tms) 604 { 605 GNUNET_break (CURLE_OK == 606 curl_easy_setopt (eh, 607 CURLOPT_TIMEOUT_MS, 608 (long) (tms + 100L))); 609 } 610 kyc->job = GNUNET_CURL_job_add (kyc->ctx, 611 eh, 612 &handle_get_kyc_finished, 613 kyc); 614 if (NULL == kyc->job) 615 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 616 return TALER_EC_NONE; 617 } 618 619 620 void 621 TALER_MERCHANT_get_private_kyc_cancel ( 622 struct TALER_MERCHANT_GetPrivateKycHandle *kyc) 623 { 624 if (NULL != kyc->job) 625 { 626 GNUNET_CURL_job_cancel (kyc->job); 627 kyc->job = NULL; 628 } 629 GNUNET_free (kyc->url); 630 GNUNET_free (kyc->exchange_url); 631 GNUNET_free (kyc->instance_id); 632 GNUNET_free (kyc->lp_status); 633 GNUNET_free (kyc->lp_not_status); 634 GNUNET_free (kyc->base_url); 635 GNUNET_free (kyc); 636 } 637 638 639 /* end of merchant_api_get-private-kyc-new.c */