merchant_api_get_kyc.c (14342B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023--2024 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_kyc.c 19 * @brief Implementation of the GET /kyc request of the merchant's HTTP API 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_service.h" 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 #include <taler/taler_signatures.h> 32 33 34 /** 35 * Maximum length of the KYC arrays supported. 36 */ 37 #define MAX_KYC 1024 38 39 /** 40 * Handle for a GET /kyc operation. 41 */ 42 struct TALER_MERCHANT_KycGetHandle 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_MERCHANT_KycGetCallback cb; 58 59 /** 60 * Closure for @a cb. 61 */ 62 void *cb_cls; 63 64 /** 65 * Reference to the execution context. 66 */ 67 struct GNUNET_CURL_Context *ctx; 68 69 }; 70 71 72 /** 73 * Parse @a kyc response and call the continuation on success. 74 * 75 * @param kyc operation handle 76 * @param[in,out] kr response details 77 * @param jkyc array from the reply 78 * @return #GNUNET_OK on success (callback was called) 79 */ 80 static enum GNUNET_GenericReturnValue 81 parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, 82 struct TALER_MERCHANT_KycResponse *kr, 83 const json_t *jkyc) 84 { 85 unsigned int num_kycs = (unsigned int) json_array_size (jkyc); 86 unsigned int num_limits = 0; 87 unsigned int num_kycauths = 0; 88 unsigned int pos_limits = 0; 89 unsigned int pos_kycauths = 0; 90 91 if ( (json_array_size (jkyc) != (size_t) num_kycs) || 92 (num_kycs > MAX_KYC) ) 93 { 94 GNUNET_break (0); 95 return GNUNET_SYSERR; 96 } 97 98 for (unsigned int i = 0; i<num_kycs; i++) 99 { 100 const json_t *jlimits = NULL; 101 const json_t *jkycauths = NULL; 102 struct GNUNET_JSON_Specification spec[] = { 103 GNUNET_JSON_spec_mark_optional ( 104 GNUNET_JSON_spec_array_const ( 105 "limits", 106 &jlimits), 107 NULL), 108 GNUNET_JSON_spec_mark_optional ( 109 GNUNET_JSON_spec_array_const ( 110 "payto_kycauths", 111 &jkycauths), 112 NULL), 113 GNUNET_JSON_spec_end () 114 }; 115 116 if (GNUNET_OK != 117 GNUNET_JSON_parse (json_array_get (jkyc, 118 i), 119 spec, 120 NULL, NULL)) 121 { 122 GNUNET_break (0); 123 json_dumpf (json_array_get (jkyc, 124 i), 125 stderr, 126 JSON_INDENT (2)); 127 return GNUNET_SYSERR; 128 } 129 num_limits += json_array_size (jlimits); 130 num_kycauths += json_array_size (jkycauths); 131 } 132 133 134 { 135 struct TALER_MERCHANT_AccountKycRedirectDetail kycs[ 136 GNUNET_NZL (num_kycs)]; 137 struct TALER_EXCHANGE_AccountLimit limits[ 138 GNUNET_NZL (num_limits)]; 139 struct TALER_FullPayto payto_kycauths[ 140 GNUNET_NZL (num_kycauths)]; 141 142 memset (kycs, 143 0, 144 sizeof (kycs)); 145 for (unsigned int i = 0; i<num_kycs; i++) 146 { 147 struct TALER_MERCHANT_AccountKycRedirectDetail *rd 148 = &kycs[i]; 149 const json_t *jlimits = NULL; 150 const json_t *jkycauths = NULL; 151 uint32_t hs; 152 struct GNUNET_JSON_Specification spec[] = { 153 TALER_JSON_spec_full_payto_uri ( 154 "payto_uri", 155 &rd->payto_uri), 156 TALER_JSON_spec_web_url ( 157 "exchange_url", 158 &rd->exchange_url), 159 GNUNET_JSON_spec_uint32 ( 160 "exchange_http_status", 161 &hs), 162 GNUNET_JSON_spec_bool ( 163 "no_keys", 164 &rd->no_keys), 165 GNUNET_JSON_spec_bool ( 166 "auth_conflict", 167 &rd->auth_conflict), 168 GNUNET_JSON_spec_mark_optional ( 169 TALER_JSON_spec_ec ( 170 "exchange_code", 171 &rd->exchange_code), 172 NULL), 173 GNUNET_JSON_spec_mark_optional ( 174 GNUNET_JSON_spec_fixed_auto ( 175 "access_token", 176 &rd->access_token), 177 &rd->no_access_token), 178 GNUNET_JSON_spec_mark_optional ( 179 GNUNET_JSON_spec_array_const ( 180 "limits", 181 &jlimits), 182 NULL), 183 GNUNET_JSON_spec_mark_optional ( 184 GNUNET_JSON_spec_array_const ( 185 "payto_kycauths", 186 &jkycauths), 187 NULL), 188 GNUNET_JSON_spec_end () 189 }; 190 size_t j; 191 json_t *jlimit; 192 json_t *jkycauth; 193 194 if (GNUNET_OK != 195 GNUNET_JSON_parse (json_array_get (jkyc, 196 i), 197 spec, 198 NULL, NULL)) 199 { 200 GNUNET_break (0); 201 json_dumpf (json_array_get (jkyc, 202 i), 203 stderr, 204 JSON_INDENT (2)); 205 return GNUNET_SYSERR; 206 } 207 rd->exchange_http_status = (unsigned int) hs; 208 rd->limits = &limits[pos_limits]; 209 rd->limits_length = json_array_size (jlimits); 210 json_array_foreach (jlimits, j, jlimit) 211 { 212 struct TALER_EXCHANGE_AccountLimit *limit 213 = &limits[pos_limits]; 214 struct GNUNET_JSON_Specification jspec[] = { 215 TALER_JSON_spec_kycte ( 216 "operation_type", 217 &limit->operation_type), 218 GNUNET_JSON_spec_relative_time ( 219 "timeframe", 220 &limit->timeframe), 221 TALER_JSON_spec_amount_any ( 222 "threshold", 223 &limit->threshold), 224 GNUNET_JSON_spec_mark_optional ( 225 GNUNET_JSON_spec_bool ( 226 "soft_limit", 227 &limit->soft_limit), 228 NULL), 229 GNUNET_JSON_spec_end () 230 }; 231 232 GNUNET_assert (pos_limits < num_limits); 233 limit->soft_limit = false; 234 if (GNUNET_OK != 235 GNUNET_JSON_parse (jlimit, 236 jspec, 237 NULL, NULL)) 238 { 239 GNUNET_break (0); 240 json_dumpf (json_array_get (jkyc, 241 i), 242 stderr, 243 JSON_INDENT (2)); 244 return GNUNET_SYSERR; 245 } 246 pos_limits++; 247 } 248 rd->payto_kycauths = &payto_kycauths[pos_kycauths]; 249 rd->pkycauth_length = json_array_size (jkycauths); 250 json_array_foreach (jkycauths, j, jkycauth) 251 { 252 GNUNET_assert (pos_kycauths < num_kycauths); 253 payto_kycauths[pos_kycauths].full_payto 254 = (char *) json_string_value (jkycauth); 255 if (NULL == payto_kycauths[pos_kycauths].full_payto) 256 { 257 GNUNET_break (0); 258 json_dumpf (json_array_get (jkyc, 259 i), 260 stderr, 261 JSON_INDENT (2)); 262 return GNUNET_SYSERR; 263 } 264 pos_kycauths++; 265 } 266 } 267 kr->details.ok.kycs = kycs; 268 kr->details.ok.kycs_length = num_kycs; 269 kyc->cb (kyc->cb_cls, 270 kr); 271 } 272 return GNUNET_OK; 273 } 274 275 276 /** 277 * Function called when we're done processing the 278 * HTTP /kyc request. 279 * 280 * @param cls the `struct TALER_MERCHANT_KycGetHandle` 281 * @param response_code HTTP response code, 0 on error 282 * @param response response body, NULL if not in JSON 283 */ 284 static void 285 handle_get_kyc_finished (void *cls, 286 long response_code, 287 const void *response) 288 { 289 struct TALER_MERCHANT_KycGetHandle *kyc = cls; 290 const json_t *json = response; 291 struct TALER_MERCHANT_KycResponse kr = { 292 .hr.http_status = (unsigned int) response_code, 293 .hr.reply = json 294 }; 295 296 kyc->job = NULL; 297 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 298 "Got /kyc response with status code %u\n", 299 (unsigned int) response_code); 300 switch (response_code) 301 { 302 case MHD_HTTP_OK: 303 { 304 const json_t *jkyc; 305 struct GNUNET_JSON_Specification spec[] = { 306 GNUNET_JSON_spec_array_const ("kyc_data", 307 &jkyc), 308 GNUNET_JSON_spec_end () 309 }; 310 311 if (GNUNET_OK != 312 GNUNET_JSON_parse (json, 313 spec, 314 NULL, NULL)) 315 { 316 kr.hr.http_status = 0; 317 kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 318 break; 319 } 320 if (GNUNET_OK != 321 parse_kyc (kyc, 322 &kr, 323 jkyc)) 324 { 325 kr.hr.http_status = 0; 326 kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 327 break; 328 } 329 /* parse_kyc called the continuation already */ 330 TALER_MERCHANT_kyc_get_cancel (kyc); 331 return; 332 } 333 case MHD_HTTP_NO_CONTENT: 334 break; 335 case MHD_HTTP_UNAUTHORIZED: 336 kr.hr.ec = TALER_JSON_get_error_code (json); 337 kr.hr.hint = TALER_JSON_get_error_hint (json); 338 /* Nothing really to verify, merchant says we need to authenticate. */ 339 break; 340 case MHD_HTTP_SERVICE_UNAVAILABLE: 341 break; 342 default: 343 /* unexpected response code */ 344 kr.hr.ec = TALER_JSON_get_error_code (json); 345 kr.hr.hint = TALER_JSON_get_error_hint (json); 346 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 347 "Unexpected response code %u/%d\n", 348 (unsigned int) response_code, 349 (int) kr.hr.ec); 350 break; 351 } 352 kyc->cb (kyc->cb_cls, 353 &kr); 354 TALER_MERCHANT_kyc_get_cancel (kyc); 355 } 356 357 358 /** 359 * Issue a GET KYC request to the backend. 360 * Returns KYC status of bank accounts. 361 * 362 * @param ctx execution context 363 * @param[in] url URL to use for the request, consumed! 364 * @param h_wire which bank account to query, NULL for all 365 * @param exchange_url which exchange to query, NULL for all 366 * @param lpt target for long polling 367 * @param timeout how long to wait for a reply 368 * @param cb function to call with the result 369 * @param cb_cls closure for @a cb 370 * @return handle for this operation, NULL upon errors 371 */ 372 static struct TALER_MERCHANT_KycGetHandle * 373 kyc_get (struct GNUNET_CURL_Context *ctx, 374 char *url, 375 const struct TALER_MerchantWireHashP *h_wire, 376 const char *exchange_url, 377 enum TALER_EXCHANGE_KycLongPollTarget lpt, 378 struct GNUNET_TIME_Relative timeout, 379 TALER_MERCHANT_KycGetCallback cb, 380 void *cb_cls) 381 { 382 struct TALER_MERCHANT_KycGetHandle *kyc; 383 CURL *eh; 384 char timeout_ms[32]; 385 char lpt_str[32]; 386 unsigned long long tms; 387 388 kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle); 389 kyc->ctx = ctx; 390 kyc->cb = cb; 391 kyc->cb_cls = cb_cls; 392 GNUNET_snprintf (lpt_str, 393 sizeof (lpt_str), 394 "%d", 395 (int) lpt); 396 tms = timeout.rel_value_us 397 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 398 GNUNET_snprintf (timeout_ms, 399 sizeof (timeout_ms), 400 "%llu", 401 tms); 402 kyc->url 403 = TALER_url_join ( 404 url, 405 "kyc", 406 "h_wire", 407 NULL == h_wire 408 ? NULL 409 : GNUNET_h2s_full (&h_wire->hash), 410 "exchange_url", 411 NULL == exchange_url 412 ? NULL 413 : exchange_url, 414 "timeout_ms", 415 GNUNET_TIME_relative_is_zero (timeout) 416 ? NULL 417 : timeout_ms, 418 "lpt", 419 TALER_EXCHANGE_KLPT_NONE == lpt 420 ? NULL 421 : lpt_str, 422 NULL); 423 GNUNET_free (url); 424 if (NULL == kyc->url) 425 { 426 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 427 "Could not construct request URL.\n"); 428 GNUNET_free (kyc); 429 return NULL; 430 } 431 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 432 "Requesting URL '%s'\n", 433 kyc->url); 434 eh = TALER_MERCHANT_curl_easy_get_ (kyc->url); 435 if (0 != tms) 436 { 437 GNUNET_break (CURLE_OK == 438 curl_easy_setopt (eh, 439 CURLOPT_TIMEOUT_MS, 440 (long) (tms + 100L))); 441 } 442 kyc->job 443 = GNUNET_CURL_job_add (ctx, 444 eh, 445 &handle_get_kyc_finished, 446 kyc); 447 return kyc; 448 } 449 450 451 struct TALER_MERCHANT_KycGetHandle * 452 TALER_MERCHANT_kyc_get ( 453 struct GNUNET_CURL_Context *ctx, 454 const char *backend_url, 455 const struct TALER_MerchantWireHashP *h_wire, 456 const char *exchange_url, 457 enum TALER_EXCHANGE_KycLongPollTarget lpt, 458 struct GNUNET_TIME_Relative timeout, 459 TALER_MERCHANT_KycGetCallback cb, 460 void *cb_cls) 461 { 462 char *url; 463 464 GNUNET_asprintf (&url, 465 "%sprivate/", 466 backend_url); 467 return kyc_get (ctx, 468 url, /* consumed! */ 469 h_wire, 470 exchange_url, 471 lpt, 472 timeout, 473 cb, 474 cb_cls); 475 } 476 477 478 struct TALER_MERCHANT_KycGetHandle * 479 TALER_MERCHANT_management_kyc_get ( 480 struct GNUNET_CURL_Context *ctx, 481 const char *backend_url, 482 const char *instance_id, 483 const struct TALER_MerchantWireHashP *h_wire, 484 const char *exchange_url, 485 enum TALER_EXCHANGE_KycLongPollTarget lpt, 486 struct GNUNET_TIME_Relative timeout, 487 TALER_MERCHANT_KycGetCallback cb, 488 void *cb_cls) 489 { 490 char *url; 491 492 GNUNET_asprintf (&url, 493 "%smanagement/instances/%s/", 494 backend_url, 495 instance_id); 496 return kyc_get (ctx, 497 url, /* consumed! */ 498 h_wire, 499 exchange_url, 500 lpt, 501 timeout, 502 cb, 503 cb_cls); 504 } 505 506 507 void 508 TALER_MERCHANT_kyc_get_cancel ( 509 struct TALER_MERCHANT_KycGetHandle *kyc) 510 { 511 if (NULL != kyc->job) 512 GNUNET_CURL_job_cancel (kyc->job); 513 GNUNET_free (kyc->url); 514 GNUNET_free (kyc); 515 }