exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c (13669B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2021-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-kyc-check-H_NORMALIZED_PAYTO.c 19 * @brief Implementation of the /kyc-check request 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <microhttpd.h> /* just for HTTP check 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 "taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h" 29 #include "taler/taler_signatures.h" 30 #include "exchange_api_curl_defaults.h" 31 32 33 /** 34 * @brief A GET /kyc-check/$H_NORMALIZED_PAYTO handle 35 */ 36 struct TALER_EXCHANGE_GetKycCheckHandle 37 { 38 39 /** 40 * The base URL for this request. 41 */ 42 char *base_url; 43 44 /** 45 * The full URL for this request, set during _start. 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_GetKycCheckCallback cb; 58 59 /** 60 * Closure for @e cb. 61 */ 62 TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls; 63 64 /** 65 * Reference to the execution context. 66 */ 67 struct GNUNET_CURL_Context *ctx; 68 69 /** 70 * Hash of the payto URI we are checking. 71 */ 72 struct TALER_NormalizedPaytoHashP h_payto; 73 74 /** 75 * Private key to authorize the request. 76 */ 77 union TALER_AccountPrivateKeyP account_priv; 78 79 /** 80 * Long polling target. 81 */ 82 enum TALER_EXCHANGE_KycLongPollTarget lpt; 83 84 /** 85 * Latest known rule generation (for long polling). 86 */ 87 uint64_t known_rule_gen; 88 89 /** 90 * Long polling timeout. 91 */ 92 struct GNUNET_TIME_Relative timeout; 93 94 }; 95 96 97 /** 98 * Parse an account KYC status from JSON and invoke the callback. 99 * 100 * @param[in,out] gkch handle 101 * @param j JSON to parse 102 * @param res response to fill 103 * @param status account status field within @a res to fill 104 * @return #GNUNET_OK on success 105 */ 106 static enum GNUNET_GenericReturnValue 107 parse_account_status ( 108 struct TALER_EXCHANGE_GetKycCheckHandle *gkch, 109 const json_t *j, 110 struct TALER_EXCHANGE_GetKycCheckResponse *res, 111 struct TALER_EXCHANGE_AccountKycStatus *status) 112 { 113 const json_t *limits = NULL; 114 struct GNUNET_JSON_Specification spec[] = { 115 GNUNET_JSON_spec_bool ("aml_review", 116 &status->aml_review), 117 GNUNET_JSON_spec_uint64 ("rule_gen", 118 &status->rule_gen), 119 GNUNET_JSON_spec_fixed_auto ("access_token", 120 &status->access_token), 121 GNUNET_JSON_spec_mark_optional ( 122 GNUNET_JSON_spec_array_const ("limits", 123 &limits), 124 NULL), 125 GNUNET_JSON_spec_end () 126 }; 127 128 if (GNUNET_OK != 129 GNUNET_JSON_parse (j, 130 spec, 131 NULL, NULL)) 132 { 133 GNUNET_break_op (0); 134 return GNUNET_SYSERR; 135 } 136 if ( (NULL != limits) && 137 (0 != json_array_size (limits)) ) 138 { 139 size_t limit_length = json_array_size (limits); 140 struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)]; 141 size_t i; 142 json_t *limit; 143 144 json_array_foreach (limits, i, limit) 145 { 146 struct TALER_EXCHANGE_AccountLimit *al = &ala[i]; 147 struct GNUNET_JSON_Specification ispec[] = { 148 GNUNET_JSON_spec_mark_optional ( 149 GNUNET_JSON_spec_bool ("soft_limit", 150 &al->soft_limit), 151 NULL), 152 GNUNET_JSON_spec_relative_time ("timeframe", 153 &al->timeframe), 154 TALER_JSON_spec_kycte ("operation_type", 155 &al->operation_type), 156 TALER_JSON_spec_amount_any ("threshold", 157 &al->threshold), 158 GNUNET_JSON_spec_end () 159 }; 160 161 al->soft_limit = false; 162 if (GNUNET_OK != 163 GNUNET_JSON_parse (limit, 164 ispec, 165 NULL, NULL)) 166 { 167 GNUNET_break_op (0); 168 return GNUNET_SYSERR; 169 } 170 } 171 status->limits = ala; 172 status->limits_length = limit_length; 173 gkch->cb (gkch->cb_cls, 174 res); 175 } 176 else 177 { 178 gkch->cb (gkch->cb_cls, 179 res); 180 } 181 GNUNET_JSON_parse_free (spec); 182 return GNUNET_OK; 183 } 184 185 186 /** 187 * Function called when we're done processing the 188 * HTTP GET /kyc-check/$H_NORMALIZED_PAYTO request. 189 * 190 * @param cls the `struct TALER_EXCHANGE_GetKycCheckHandle` 191 * @param response_code HTTP response code, 0 on error 192 * @param response parsed JSON result, NULL on error 193 */ 194 static void 195 handle_kyc_check_finished (void *cls, 196 long response_code, 197 const void *response) 198 { 199 struct TALER_EXCHANGE_GetKycCheckHandle *gkch = cls; 200 const json_t *j = response; 201 struct TALER_EXCHANGE_GetKycCheckResponse ks = { 202 .hr.reply = j, 203 .hr.http_status = (unsigned int) response_code 204 }; 205 206 gkch->job = NULL; 207 switch (response_code) 208 { 209 case 0: 210 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 211 break; 212 case MHD_HTTP_OK: 213 { 214 if (GNUNET_OK != 215 parse_account_status (gkch, 216 j, 217 &ks, 218 &ks.details.ok)) 219 { 220 GNUNET_break_op (0); 221 ks.hr.http_status = 0; 222 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 223 break; 224 } 225 TALER_EXCHANGE_get_kyc_check_cancel (gkch); 226 return; 227 } 228 case MHD_HTTP_ACCEPTED: 229 { 230 if (GNUNET_OK != 231 parse_account_status (gkch, 232 j, 233 &ks, 234 &ks.details.accepted)) 235 { 236 GNUNET_break_op (0); 237 ks.hr.http_status = 0; 238 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 239 break; 240 } 241 TALER_EXCHANGE_get_kyc_check_cancel (gkch); 242 return; 243 } 244 case MHD_HTTP_NO_CONTENT: 245 break; 246 case MHD_HTTP_BAD_REQUEST: 247 ks.hr.ec = TALER_JSON_get_error_code (j); 248 break; 249 case MHD_HTTP_FORBIDDEN: 250 { 251 struct GNUNET_JSON_Specification spec[] = { 252 GNUNET_JSON_spec_fixed_auto ( 253 "expected_account_pub", 254 &ks.details.forbidden.expected_account_pub), 255 TALER_JSON_spec_ec ("code", 256 &ks.hr.ec), 257 GNUNET_JSON_spec_end () 258 }; 259 260 if (GNUNET_OK != 261 GNUNET_JSON_parse (j, 262 spec, 263 NULL, NULL)) 264 { 265 GNUNET_break_op (0); 266 ks.hr.http_status = 0; 267 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 268 break; 269 } 270 break; 271 } 272 case MHD_HTTP_NOT_FOUND: 273 ks.hr.ec = TALER_JSON_get_error_code (j); 274 break; 275 case MHD_HTTP_CONFLICT: 276 ks.hr.ec = TALER_JSON_get_error_code (j); 277 break; 278 case MHD_HTTP_INTERNAL_SERVER_ERROR: 279 ks.hr.ec = TALER_JSON_get_error_code (j); 280 break; 281 default: 282 /* unexpected response code */ 283 GNUNET_break_op (0); 284 ks.hr.ec = TALER_JSON_get_error_code (j); 285 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 286 "Unexpected response code %u/%d for exchange kyc_check\n", 287 (unsigned int) response_code, 288 (int) ks.hr.ec); 289 break; 290 } 291 if (NULL != gkch->cb) 292 { 293 gkch->cb (gkch->cb_cls, 294 &ks); 295 gkch->cb = NULL; 296 } 297 TALER_EXCHANGE_get_kyc_check_cancel (gkch); 298 } 299 300 301 struct TALER_EXCHANGE_GetKycCheckHandle * 302 TALER_EXCHANGE_get_kyc_check_create ( 303 struct GNUNET_CURL_Context *ctx, 304 const char *url, 305 const struct TALER_NormalizedPaytoHashP *h_payto, 306 const union TALER_AccountPrivateKeyP *pk) 307 { 308 struct TALER_EXCHANGE_GetKycCheckHandle *gkch; 309 310 gkch = GNUNET_new (struct TALER_EXCHANGE_GetKycCheckHandle); 311 gkch->ctx = ctx; 312 gkch->base_url = GNUNET_strdup (url); 313 gkch->h_payto = *h_payto; 314 gkch->account_priv = *pk; 315 return gkch; 316 } 317 318 319 enum GNUNET_GenericReturnValue 320 TALER_EXCHANGE_get_kyc_check_set_options_ ( 321 struct TALER_EXCHANGE_GetKycCheckHandle *gkch, 322 unsigned int num_options, 323 const struct TALER_EXCHANGE_GetKycCheckOptionValue *options) 324 { 325 for (unsigned int i = 0; i < num_options; i++) 326 { 327 const struct TALER_EXCHANGE_GetKycCheckOptionValue *opt = &options[i]; 328 329 switch (opt->option) 330 { 331 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_END: 332 return GNUNET_OK; 333 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_KNOWN_RULE_GEN: 334 gkch->known_rule_gen = opt->details.known_rule_gen; 335 break; 336 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_LPT: 337 gkch->lpt = opt->details.lpt; 338 break; 339 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_TIMEOUT: 340 gkch->timeout = opt->details.timeout; 341 break; 342 default: 343 GNUNET_break (0); 344 return GNUNET_SYSERR; 345 } 346 } 347 return GNUNET_OK; 348 } 349 350 351 enum TALER_ErrorCode 352 TALER_EXCHANGE_get_kyc_check_start ( 353 struct TALER_EXCHANGE_GetKycCheckHandle *gkch, 354 TALER_EXCHANGE_GetKycCheckCallback cb, 355 TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls) 356 { 357 CURL *eh; 358 char arg_str[128]; 359 char timeout_ms[32]; 360 char lpt_str[32]; 361 char krg_str[32]; 362 struct curl_slist *job_headers = NULL; 363 unsigned long long tms; 364 365 gkch->cb = cb; 366 gkch->cb_cls = cb_cls; 367 { 368 char *hps; 369 370 hps = GNUNET_STRINGS_data_to_string_alloc ( 371 &gkch->h_payto, 372 sizeof (gkch->h_payto)); 373 GNUNET_snprintf (arg_str, 374 sizeof (arg_str), 375 "kyc-check/%s", 376 hps); 377 GNUNET_free (hps); 378 } 379 tms = gkch->timeout.rel_value_us 380 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 381 GNUNET_snprintf (timeout_ms, 382 sizeof (timeout_ms), 383 "%llu", 384 tms); 385 GNUNET_snprintf (krg_str, 386 sizeof (krg_str), 387 "%llu", 388 (unsigned long long) gkch->known_rule_gen); 389 GNUNET_snprintf (lpt_str, 390 sizeof (lpt_str), 391 "%d", 392 (int) gkch->lpt); 393 gkch->url 394 = TALER_url_join ( 395 gkch->base_url, 396 arg_str, 397 "timeout_ms", 398 GNUNET_TIME_relative_is_zero (gkch->timeout) 399 ? NULL 400 : timeout_ms, 401 "min_rule", 402 0 == gkch->known_rule_gen 403 ? NULL 404 : krg_str, 405 "lpt", 406 TALER_EXCHANGE_KLPT_NONE == gkch->lpt 407 ? NULL 408 : lpt_str, 409 NULL); 410 if (NULL == gkch->url) 411 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 412 eh = TALER_EXCHANGE_curl_easy_get_ (gkch->url); 413 if (NULL == eh) 414 { 415 GNUNET_break (0); 416 GNUNET_free (gkch->url); 417 gkch->url = NULL; 418 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 419 } 420 if (0 != tms) 421 { 422 GNUNET_break (CURLE_OK == 423 curl_easy_setopt (eh, 424 CURLOPT_TIMEOUT_MS, 425 (long) (tms + 500L))); 426 } 427 job_headers 428 = curl_slist_append ( 429 job_headers, 430 "Content-Type: application/json"); 431 { 432 union TALER_AccountPublicKeyP account_pub; 433 union TALER_AccountSignatureP account_sig; 434 char *sig_hdr; 435 char *pub_hdr; 436 char *hdr; 437 438 GNUNET_CRYPTO_eddsa_key_get_public ( 439 &gkch->account_priv.reserve_priv.eddsa_priv, 440 &account_pub.reserve_pub.eddsa_pub); 441 TALER_account_kyc_auth_sign (&gkch->account_priv, 442 &account_sig); 443 444 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 445 &account_sig, 446 sizeof (account_sig)); 447 GNUNET_asprintf (&hdr, 448 "%s: %s", 449 TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE, 450 sig_hdr); 451 GNUNET_free (sig_hdr); 452 job_headers = curl_slist_append (job_headers, 453 hdr); 454 GNUNET_free (hdr); 455 456 pub_hdr = GNUNET_STRINGS_data_to_string_alloc ( 457 &account_pub, 458 sizeof (account_pub)); 459 GNUNET_asprintf (&hdr, 460 "%s: %s", 461 TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY, 462 pub_hdr); 463 GNUNET_free (pub_hdr); 464 job_headers = curl_slist_append (job_headers, 465 hdr); 466 GNUNET_free (hdr); 467 if (NULL == job_headers) 468 { 469 GNUNET_break (0); 470 curl_easy_cleanup (eh); 471 GNUNET_free (gkch->url); 472 gkch->url = NULL; 473 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 474 } 475 } 476 gkch->job 477 = GNUNET_CURL_job_add2 (gkch->ctx, 478 eh, 479 job_headers, 480 &handle_kyc_check_finished, 481 gkch); 482 curl_slist_free_all (job_headers); 483 if (NULL == gkch->job) 484 { 485 GNUNET_free (gkch->url); 486 gkch->url = NULL; 487 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 488 } 489 return TALER_EC_NONE; 490 } 491 492 493 void 494 TALER_EXCHANGE_get_kyc_check_cancel ( 495 struct TALER_EXCHANGE_GetKycCheckHandle *gkch) 496 { 497 if (NULL != gkch->job) 498 { 499 GNUNET_CURL_job_cancel (gkch->job); 500 gkch->job = NULL; 501 } 502 GNUNET_free (gkch->url); 503 GNUNET_free (gkch->base_url); 504 GNUNET_free (gkch); 505 } 506 507 508 /* end of exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c */