exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c (13745B)
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 "platform.h" /* UNNECESSARY? */ 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" /* UNNECESSARY? */ 27 #include "taler/taler_json_lib.h" 28 #include "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_string ("tos_required", 123 &status->tos_required), 124 NULL), 125 GNUNET_JSON_spec_mark_optional ( 126 GNUNET_JSON_spec_array_const ("limits", 127 &limits), 128 NULL), 129 GNUNET_JSON_spec_end () 130 }; 131 132 if (GNUNET_OK != 133 GNUNET_JSON_parse (j, 134 spec, 135 NULL, NULL)) 136 { 137 GNUNET_break_op (0); 138 return GNUNET_SYSERR; 139 } 140 if ( (NULL != limits) && 141 (0 != json_array_size (limits)) ) 142 { 143 size_t limit_length = json_array_size (limits); 144 struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)]; 145 size_t i; 146 json_t *limit; 147 148 json_array_foreach (limits, i, limit) 149 { 150 struct TALER_EXCHANGE_AccountLimit *al = &ala[i]; 151 struct GNUNET_JSON_Specification ispec[] = { 152 GNUNET_JSON_spec_mark_optional ( 153 GNUNET_JSON_spec_bool ("soft_limit", 154 &al->soft_limit), 155 NULL), 156 GNUNET_JSON_spec_relative_time ("timeframe", 157 &al->timeframe), 158 TALER_JSON_spec_kycte ("operation_type", 159 &al->operation_type), 160 TALER_JSON_spec_amount_any ("threshold", 161 &al->threshold), 162 GNUNET_JSON_spec_end () 163 }; 164 165 al->soft_limit = false; 166 if (GNUNET_OK != 167 GNUNET_JSON_parse (limit, 168 ispec, 169 NULL, NULL)) 170 { 171 GNUNET_break_op (0); 172 return GNUNET_SYSERR; 173 } 174 } 175 status->limits = ala; 176 status->limits_length = limit_length; 177 gkch->cb (gkch->cb_cls, 178 res); 179 } 180 else 181 { 182 gkch->cb (gkch->cb_cls, 183 res); 184 } 185 GNUNET_JSON_parse_free (spec); 186 return GNUNET_OK; 187 } 188 189 190 /** 191 * Function called when we're done processing the 192 * HTTP GET /kyc-check/$H_NORMALIZED_PAYTO request. 193 * 194 * @param cls the `struct TALER_EXCHANGE_GetKycCheckHandle` 195 * @param response_code HTTP response code, 0 on error 196 * @param response parsed JSON result, NULL on error 197 */ 198 static void 199 handle_kyc_check_finished (void *cls, 200 long response_code, 201 const void *response) 202 { 203 struct TALER_EXCHANGE_GetKycCheckHandle *gkch = cls; 204 const json_t *j = response; 205 struct TALER_EXCHANGE_GetKycCheckResponse ks = { 206 .hr.reply = j, 207 .hr.http_status = (unsigned int) response_code 208 }; 209 210 gkch->job = NULL; 211 switch (response_code) 212 { 213 case 0: 214 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 215 break; 216 case MHD_HTTP_OK: 217 { 218 if (GNUNET_OK != 219 parse_account_status (gkch, 220 j, 221 &ks, 222 &ks.details.ok)) 223 { 224 GNUNET_break_op (0); 225 ks.hr.http_status = 0; 226 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 227 break; 228 } 229 TALER_EXCHANGE_get_kyc_check_cancel (gkch); 230 return; 231 } 232 case MHD_HTTP_ACCEPTED: 233 { 234 if (GNUNET_OK != 235 parse_account_status (gkch, 236 j, 237 &ks, 238 &ks.details.accepted)) 239 { 240 GNUNET_break_op (0); 241 ks.hr.http_status = 0; 242 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 243 break; 244 } 245 TALER_EXCHANGE_get_kyc_check_cancel (gkch); 246 return; 247 } 248 case MHD_HTTP_NO_CONTENT: 249 break; 250 case MHD_HTTP_BAD_REQUEST: 251 ks.hr.ec = TALER_JSON_get_error_code (j); 252 break; 253 case MHD_HTTP_FORBIDDEN: 254 { 255 struct GNUNET_JSON_Specification spec[] = { 256 GNUNET_JSON_spec_fixed_auto ( 257 "expected_account_pub", 258 &ks.details.forbidden.expected_account_pub), 259 TALER_JSON_spec_ec ("code", 260 &ks.hr.ec), 261 GNUNET_JSON_spec_end () 262 }; 263 264 if (GNUNET_OK != 265 GNUNET_JSON_parse (j, 266 spec, 267 NULL, NULL)) 268 { 269 GNUNET_break_op (0); 270 ks.hr.http_status = 0; 271 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 272 break; 273 } 274 break; 275 } 276 case MHD_HTTP_NOT_FOUND: 277 ks.hr.ec = TALER_JSON_get_error_code (j); 278 break; 279 case MHD_HTTP_CONFLICT: 280 ks.hr.ec = TALER_JSON_get_error_code (j); 281 break; 282 case MHD_HTTP_INTERNAL_SERVER_ERROR: 283 ks.hr.ec = TALER_JSON_get_error_code (j); 284 break; 285 default: 286 /* unexpected response code */ 287 GNUNET_break_op (0); 288 ks.hr.ec = TALER_JSON_get_error_code (j); 289 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 290 "Unexpected response code %u/%d for exchange kyc_check\n", 291 (unsigned int) response_code, 292 (int) ks.hr.ec); 293 break; 294 } 295 if (NULL != gkch->cb) 296 { 297 gkch->cb (gkch->cb_cls, 298 &ks); 299 gkch->cb = NULL; 300 } 301 TALER_EXCHANGE_get_kyc_check_cancel (gkch); 302 } 303 304 305 struct TALER_EXCHANGE_GetKycCheckHandle * 306 TALER_EXCHANGE_get_kyc_check_create ( 307 struct GNUNET_CURL_Context *ctx, 308 const char *url, 309 const struct TALER_NormalizedPaytoHashP *h_payto, 310 const union TALER_AccountPrivateKeyP *pk) 311 { 312 struct TALER_EXCHANGE_GetKycCheckHandle *gkch; 313 314 gkch = GNUNET_new (struct TALER_EXCHANGE_GetKycCheckHandle); 315 gkch->ctx = ctx; 316 gkch->base_url = GNUNET_strdup (url); 317 gkch->h_payto = *h_payto; 318 gkch->account_priv = *pk; 319 return gkch; 320 } 321 322 323 enum GNUNET_GenericReturnValue 324 TALER_EXCHANGE_get_kyc_check_set_options_ ( 325 struct TALER_EXCHANGE_GetKycCheckHandle *gkch, 326 unsigned int num_options, 327 const struct TALER_EXCHANGE_GetKycCheckOptionValue *options) 328 { 329 for (unsigned int i = 0; i < num_options; i++) 330 { 331 const struct TALER_EXCHANGE_GetKycCheckOptionValue *opt = &options[i]; 332 333 switch (opt->option) 334 { 335 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_END: 336 return GNUNET_OK; 337 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_KNOWN_RULE_GEN: 338 gkch->known_rule_gen = opt->details.known_rule_gen; 339 break; 340 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_LPT: 341 gkch->lpt = opt->details.lpt; 342 break; 343 case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_TIMEOUT: 344 gkch->timeout = opt->details.timeout; 345 break; 346 default: 347 GNUNET_break (0); 348 return GNUNET_SYSERR; 349 } 350 } 351 return GNUNET_OK; 352 } 353 354 355 enum TALER_ErrorCode 356 TALER_EXCHANGE_get_kyc_check_start ( 357 struct TALER_EXCHANGE_GetKycCheckHandle *gkch, 358 TALER_EXCHANGE_GetKycCheckCallback cb, 359 TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls) 360 { 361 CURL *eh; 362 char arg_str[128]; 363 char timeout_ms[32]; 364 char lpt_str[32]; 365 char krg_str[32]; 366 struct curl_slist *job_headers = NULL; 367 unsigned long long tms; 368 369 gkch->cb = cb; 370 gkch->cb_cls = cb_cls; 371 { 372 char *hps; 373 374 hps = GNUNET_STRINGS_data_to_string_alloc ( 375 &gkch->h_payto, 376 sizeof (gkch->h_payto)); 377 GNUNET_snprintf (arg_str, 378 sizeof (arg_str), 379 "kyc-check/%s", 380 hps); 381 GNUNET_free (hps); 382 } 383 tms = gkch->timeout.rel_value_us 384 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 385 GNUNET_snprintf (timeout_ms, 386 sizeof (timeout_ms), 387 "%llu", 388 tms); 389 GNUNET_snprintf (krg_str, 390 sizeof (krg_str), 391 "%llu", 392 (unsigned long long) gkch->known_rule_gen); 393 GNUNET_snprintf (lpt_str, 394 sizeof (lpt_str), 395 "%d", 396 (int) gkch->lpt); 397 gkch->url 398 = TALER_url_join ( 399 gkch->base_url, 400 arg_str, 401 "timeout_ms", 402 GNUNET_TIME_relative_is_zero (gkch->timeout) 403 ? NULL 404 : timeout_ms, 405 "min_rule", 406 0 == gkch->known_rule_gen 407 ? NULL 408 : krg_str, 409 "lpt", 410 TALER_EXCHANGE_KLPT_NONE == gkch->lpt 411 ? NULL 412 : lpt_str, 413 NULL); 414 if (NULL == gkch->url) 415 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 416 eh = TALER_EXCHANGE_curl_easy_get_ (gkch->url); 417 if (NULL == eh) 418 { 419 GNUNET_break (0); 420 GNUNET_free (gkch->url); 421 gkch->url = NULL; 422 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 423 } 424 if (0 != tms) 425 { 426 GNUNET_break (CURLE_OK == 427 curl_easy_setopt (eh, 428 CURLOPT_TIMEOUT_MS, 429 (long) (tms + 500L))); 430 } 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 */