merchant_api_get-private-kyc.c (17236B)
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 merchant_api_get-private-kyc-new.c 19 * @brief Implementation of the GET /private/kyc request 20 * @author Christian Grothoff 21 */ 22 #include "taler/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/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 (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 (0); 182 return GNUNET_SYSERR; 183 } 184 num_limits += json_array_size (jlimits); 185 num_kycauths += json_array_size (jkycauths); 186 } 187 188 { 189 struct TALER_MERCHANT_GetPrivateKycRedirectDetail kycs[ 190 GNUNET_NZL (num_kycs)]; 191 struct TALER_EXCHANGE_AccountLimit limits[ 192 GNUNET_NZL (num_limits)]; 193 struct TALER_FullPayto payto_kycauths[ 194 GNUNET_NZL (num_kycauths)]; 195 196 memset (kycs, 197 0, 198 sizeof (kycs)); 199 for (unsigned int i = 0; i<num_kycs; i++) 200 { 201 struct TALER_MERCHANT_GetPrivateKycRedirectDetail *rd 202 = &kycs[i]; 203 const json_t *jlimits = NULL; 204 const json_t *jkycauths = NULL; 205 uint32_t hs; 206 struct GNUNET_JSON_Specification spec[] = { 207 TALER_JSON_spec_full_payto_uri ( 208 "payto_uri", 209 &rd->payto_uri), 210 TALER_JSON_spec_web_url ( 211 "exchange_url", 212 &rd->exchange_url), 213 GNUNET_JSON_spec_uint32 ( 214 "exchange_http_status", 215 &hs), 216 GNUNET_JSON_spec_bool ( 217 "no_keys", 218 &rd->no_keys), 219 GNUNET_JSON_spec_bool ( 220 "auth_conflict", 221 &rd->auth_conflict), 222 GNUNET_JSON_spec_mark_optional ( 223 TALER_JSON_spec_ec ( 224 "exchange_code", 225 &rd->exchange_code), 226 NULL), 227 GNUNET_JSON_spec_mark_optional ( 228 GNUNET_JSON_spec_fixed_auto ( 229 "access_token", 230 &rd->access_token), 231 &rd->no_access_token), 232 GNUNET_JSON_spec_fixed_auto ( 233 "h_wire", 234 &rd->h_wire), 235 GNUNET_JSON_spec_mark_optional ( 236 GNUNET_JSON_spec_string ( 237 "status", 238 &rd->status), 239 NULL), 240 /* Mandatory since **v25** */ 241 GNUNET_JSON_spec_mark_optional ( 242 GNUNET_JSON_spec_string ( 243 "exchange_currency", 244 &rd->exchange_currency), 245 NULL), 246 GNUNET_JSON_spec_mark_optional ( 247 GNUNET_JSON_spec_array_const ( 248 "limits", 249 &jlimits), 250 NULL), 251 GNUNET_JSON_spec_mark_optional ( 252 GNUNET_JSON_spec_array_const ( 253 "payto_kycauths", 254 &jkycauths), 255 NULL), 256 GNUNET_JSON_spec_end () 257 }; 258 size_t j; 259 json_t *jlimit; 260 json_t *jkycauth; 261 262 if (GNUNET_OK != 263 GNUNET_JSON_parse (json_array_get (jkyc, 264 i), 265 spec, 266 NULL, NULL)) 267 { 268 GNUNET_break (0); 269 return GNUNET_SYSERR; 270 } 271 rd->exchange_http_status = (unsigned int) hs; 272 rd->limits = &limits[pos_limits]; 273 rd->limits_length = json_array_size (jlimits); 274 json_array_foreach (jlimits, j, jlimit) 275 { 276 struct TALER_EXCHANGE_AccountLimit *limit 277 = &limits[pos_limits]; 278 struct GNUNET_JSON_Specification jspec[] = { 279 TALER_JSON_spec_kycte ( 280 "operation_type", 281 &limit->operation_type), 282 GNUNET_JSON_spec_relative_time ( 283 "timeframe", 284 &limit->timeframe), 285 TALER_JSON_spec_amount_any ( 286 "threshold", 287 &limit->threshold), 288 GNUNET_JSON_spec_mark_optional ( 289 GNUNET_JSON_spec_bool ( 290 "soft_limit", 291 &limit->soft_limit), 292 NULL), 293 GNUNET_JSON_spec_end () 294 }; 295 296 GNUNET_assert (pos_limits < num_limits); 297 limit->soft_limit = false; 298 if (GNUNET_OK != 299 GNUNET_JSON_parse (jlimit, 300 jspec, 301 NULL, NULL)) 302 { 303 GNUNET_break (0); 304 return GNUNET_SYSERR; 305 } 306 pos_limits++; 307 } 308 rd->payto_kycauths = &payto_kycauths[pos_kycauths]; 309 rd->pkycauth_length = json_array_size (jkycauths); 310 json_array_foreach (jkycauths, j, jkycauth) 311 { 312 GNUNET_assert (pos_kycauths < num_kycauths); 313 payto_kycauths[pos_kycauths].full_payto 314 = (char *) json_string_value (jkycauth); 315 if (NULL == payto_kycauths[pos_kycauths].full_payto) 316 { 317 GNUNET_break (0); 318 return GNUNET_SYSERR; 319 } 320 pos_kycauths++; 321 } 322 } 323 kr->details.ok.kycs = kycs; 324 kr->details.ok.kycs_length = num_kycs; 325 kyc->cb (kyc->cb_cls, 326 kr); 327 kyc->cb = NULL; 328 } 329 return GNUNET_OK; 330 } 331 332 333 /** 334 * Function called when we're done processing the 335 * HTTP GET /private/kyc request. 336 * 337 * @param cls the `struct TALER_MERCHANT_GetPrivateKycHandle` 338 * @param response_code HTTP response code, 0 on error 339 * @param response response body, NULL if not in JSON 340 */ 341 static void 342 handle_get_kyc_finished (void *cls, 343 long response_code, 344 const void *response) 345 { 346 struct TALER_MERCHANT_GetPrivateKycHandle *kyc = cls; 347 const json_t *json = response; 348 struct TALER_MERCHANT_GetPrivateKycResponse kr = { 349 .hr.http_status = (unsigned int) response_code, 350 .hr.reply = json 351 }; 352 353 kyc->job = NULL; 354 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 355 "Got /private/kyc response with status code %u\n", 356 (unsigned int) response_code); 357 switch (response_code) 358 { 359 case MHD_HTTP_OK: 360 { 361 const json_t *jkyc; 362 struct GNUNET_JSON_Specification spec[] = { 363 GNUNET_JSON_spec_array_const ("kyc_data", 364 &jkyc), 365 GNUNET_JSON_spec_end () 366 }; 367 368 if (GNUNET_OK != 369 GNUNET_JSON_parse (json, 370 spec, 371 NULL, NULL)) 372 { 373 kr.hr.http_status = 0; 374 kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 375 break; 376 } 377 if (GNUNET_OK != 378 parse_kyc (kyc, 379 &kr, 380 jkyc)) 381 { 382 kr.hr.http_status = 0; 383 kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 384 break; 385 } 386 /* parse_kyc called the continuation already */ 387 TALER_MERCHANT_get_private_kyc_cancel (kyc); 388 return; 389 } 390 case MHD_HTTP_NO_CONTENT: 391 break; 392 case MHD_HTTP_NOT_MODIFIED: 393 /* ETag matched; nothing changed. No body expected. */ 394 break; 395 case MHD_HTTP_BAD_REQUEST: 396 kr.hr.ec = TALER_JSON_get_error_code (json); 397 kr.hr.hint = TALER_JSON_get_error_hint (json); 398 break; 399 case MHD_HTTP_UNAUTHORIZED: 400 kr.hr.ec = TALER_JSON_get_error_code (json); 401 kr.hr.hint = TALER_JSON_get_error_hint (json); 402 break; 403 case MHD_HTTP_NOT_FOUND: 404 kr.hr.ec = TALER_JSON_get_error_code (json); 405 kr.hr.hint = TALER_JSON_get_error_hint (json); 406 break; 407 case MHD_HTTP_NOT_ACCEPTABLE: 408 kr.hr.ec = TALER_JSON_get_error_code (json); 409 kr.hr.hint = TALER_JSON_get_error_hint (json); 410 break; 411 case MHD_HTTP_SERVICE_UNAVAILABLE: 412 break; 413 default: 414 kr.hr.ec = TALER_JSON_get_error_code (json); 415 kr.hr.hint = TALER_JSON_get_error_hint (json); 416 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 417 "Unexpected response code %u/%d\n", 418 (unsigned int) response_code, 419 (int) kr.hr.ec); 420 break; 421 } 422 kyc->cb (kyc->cb_cls, 423 &kr); 424 TALER_MERCHANT_get_private_kyc_cancel (kyc); 425 } 426 427 428 struct TALER_MERCHANT_GetPrivateKycHandle * 429 TALER_MERCHANT_get_private_kyc_create ( 430 struct GNUNET_CURL_Context *ctx, 431 const char *url) 432 { 433 struct TALER_MERCHANT_GetPrivateKycHandle *kyc; 434 435 kyc = GNUNET_new (struct TALER_MERCHANT_GetPrivateKycHandle); 436 kyc->ctx = ctx; 437 kyc->base_url = GNUNET_strdup (url); 438 kyc->lpt = TALER_EXCHANGE_KLPT_NONE; 439 return kyc; 440 } 441 442 443 enum GNUNET_GenericReturnValue 444 TALER_MERCHANT_get_private_kyc_set_options_ ( 445 struct TALER_MERCHANT_GetPrivateKycHandle *kyc, 446 unsigned int num_options, 447 const struct TALER_MERCHANT_GetPrivateKycOptionValue *options) 448 { 449 for (unsigned int i = 0; i < num_options; i++) 450 { 451 const struct TALER_MERCHANT_GetPrivateKycOptionValue *opt = 452 &options[i]; 453 454 switch (opt->option) 455 { 456 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_END: 457 return GNUNET_OK; 458 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_H_WIRE: 459 if (NULL != opt->details.h_wire) 460 { 461 kyc->h_wire_val = *opt->details.h_wire; 462 kyc->h_wire = &kyc->h_wire_val; 463 kyc->have_h_wire = true; 464 } 465 break; 466 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_EXCHANGE_URL: 467 GNUNET_free (kyc->exchange_url); 468 if (NULL != opt->details.exchange_url) 469 kyc->exchange_url = GNUNET_strdup (opt->details.exchange_url); 470 break; 471 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LPT: 472 kyc->lpt = opt->details.lpt; 473 break; 474 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_TIMEOUT: 475 kyc->timeout = opt->details.timeout; 476 break; 477 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_INSTANCE_ID: 478 GNUNET_free (kyc->instance_id); 479 if (NULL != opt->details.instance_id) 480 kyc->instance_id = GNUNET_strdup (opt->details.instance_id); 481 break; 482 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_STATUS: 483 GNUNET_free (kyc->lp_status); 484 if (NULL != opt->details.lp_status) 485 kyc->lp_status = GNUNET_strdup (opt->details.lp_status); 486 break; 487 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_STATUS: 488 GNUNET_free (kyc->lp_not_status); 489 if (NULL != opt->details.lp_not_status) 490 kyc->lp_not_status = GNUNET_strdup (opt->details.lp_not_status); 491 break; 492 case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_ETAG: 493 if (NULL != opt->details.lp_not_etag) 494 { 495 kyc->lp_not_etag = *opt->details.lp_not_etag; 496 kyc->have_lp_not_etag = true; 497 } 498 break; 499 default: 500 GNUNET_break (0); 501 return GNUNET_NO; 502 } 503 } 504 return GNUNET_OK; 505 } 506 507 508 enum TALER_ErrorCode 509 TALER_MERCHANT_get_private_kyc_start ( 510 struct TALER_MERCHANT_GetPrivateKycHandle *kyc, 511 TALER_MERCHANT_GetPrivateKycCallback cb, 512 TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls) 513 { 514 CURL *eh; 515 unsigned long long tms; 516 char timeout_ms[32]; 517 char lpt_str[32]; 518 char *base_path; 519 520 kyc->cb = cb; 521 kyc->cb_cls = cb_cls; 522 523 /* Build the base path depending on whether instance_id is set */ 524 if (NULL != kyc->instance_id) 525 { 526 GNUNET_asprintf (&base_path, 527 "%smanagement/instances/%s/", 528 kyc->base_url, 529 kyc->instance_id); 530 } 531 else 532 { 533 GNUNET_asprintf (&base_path, 534 "%sprivate/", 535 kyc->base_url); 536 } 537 538 GNUNET_snprintf (lpt_str, 539 sizeof (lpt_str), 540 "%d", 541 (int) kyc->lpt); 542 tms = kyc->timeout.rel_value_us 543 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 544 GNUNET_snprintf (timeout_ms, 545 sizeof (timeout_ms), 546 "%llu", 547 tms); 548 { 549 char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1]; 550 551 if (kyc->have_lp_not_etag) 552 { 553 char *end; 554 555 end = GNUNET_STRINGS_data_to_string ( 556 &kyc->lp_not_etag, 557 sizeof (kyc->lp_not_etag), 558 etag_str, 559 sizeof (etag_str) - 1); 560 *end = '\0'; 561 } 562 kyc->url 563 = TALER_url_join ( 564 base_path, 565 "kyc", 566 "h_wire", 567 kyc->have_h_wire 568 ? GNUNET_h2s_full (&kyc->h_wire_val.hash) 569 : NULL, 570 "exchange_url", 571 kyc->exchange_url, 572 "timeout_ms", 573 GNUNET_TIME_relative_is_zero (kyc->timeout) 574 ? NULL 575 : timeout_ms, 576 "lpt", 577 TALER_EXCHANGE_KLPT_NONE == kyc->lpt 578 ? NULL 579 : lpt_str, 580 "lp_status", 581 kyc->lp_status, 582 "lp_not_status", 583 kyc->lp_not_status, 584 "lp_not_etag", 585 kyc->have_lp_not_etag 586 ? etag_str 587 : NULL, 588 NULL); 589 } 590 GNUNET_free (base_path); 591 if (NULL == kyc->url) 592 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 593 eh = TALER_MERCHANT_curl_easy_get_ (kyc->url); 594 if (NULL == eh) 595 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 596 if (0 != tms) 597 { 598 GNUNET_break (CURLE_OK == 599 curl_easy_setopt (eh, 600 CURLOPT_TIMEOUT_MS, 601 (long) (tms + 100L))); 602 } 603 kyc->job = GNUNET_CURL_job_add (kyc->ctx, 604 eh, 605 &handle_get_kyc_finished, 606 kyc); 607 if (NULL == kyc->job) 608 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 609 return TALER_EC_NONE; 610 } 611 612 613 void 614 TALER_MERCHANT_get_private_kyc_cancel ( 615 struct TALER_MERCHANT_GetPrivateKycHandle *kyc) 616 { 617 if (NULL != kyc->job) 618 { 619 GNUNET_CURL_job_cancel (kyc->job); 620 kyc->job = NULL; 621 } 622 GNUNET_free (kyc->url); 623 GNUNET_free (kyc->exchange_url); 624 GNUNET_free (kyc->instance_id); 625 GNUNET_free (kyc->lp_status); 626 GNUNET_free (kyc->lp_not_status); 627 GNUNET_free (kyc->base_url); 628 GNUNET_free (kyc); 629 } 630 631 632 /* end of merchant_api_get-private-kyc-new.c */