taler-exchange-httpd_kyc-proof.c (17000B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2021-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_kyc-proof.c 18 * @brief Handle request for proof for KYC check. 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_json_lib.h> 24 #include <jansson.h> 25 #include <microhttpd.h> 26 #include "taler/taler_attributes.h" 27 #include "taler/taler_json_lib.h" 28 #include "taler/taler_kyclogic_lib.h" 29 #include "taler/taler_mhd_lib.h" 30 #include "taler/taler_templating_lib.h" 31 #include "taler-exchange-httpd_common_kyc.h" 32 #include "taler-exchange-httpd_kyc-proof.h" 33 #include "taler-exchange-httpd_responses.h" 34 35 36 /** 37 * Context for the proof. 38 */ 39 struct KycProofContext 40 { 41 42 /** 43 * Kept in a DLL while suspended. 44 */ 45 struct KycProofContext *next; 46 47 /** 48 * Kept in a DLL while suspended. 49 */ 50 struct KycProofContext *prev; 51 52 /** 53 * Details about the connection we are processing. 54 */ 55 struct TEH_RequestContext *rc; 56 57 /** 58 * Proof logic to run. 59 */ 60 struct TALER_KYCLOGIC_Plugin *logic; 61 62 /** 63 * Configuration for @a logic. 64 */ 65 struct TALER_KYCLOGIC_ProviderDetails *pd; 66 67 /** 68 * Asynchronous operation with the proof system. 69 */ 70 struct TALER_KYCLOGIC_ProofHandle *ph; 71 72 /** 73 * KYC AML trigger operation. 74 */ 75 struct TEH_KycMeasureRunContext *kat; 76 77 /** 78 * Process information about the user for the plugin from the database, can 79 * be NULL. 80 */ 81 char *provider_user_id; 82 83 /** 84 * Process information about the legitimization process for the plugin from the 85 * database, can be NULL. 86 */ 87 char *provider_legitimization_id; 88 89 /** 90 * Hash of payment target URI this is about. 91 */ 92 struct TALER_NormalizedPaytoHashP h_payto; 93 94 /** 95 * Final HTTP response to return. 96 */ 97 struct MHD_Response *response; 98 99 /** 100 * Final HTTP response code to return. 101 */ 102 unsigned int response_code; 103 104 /** 105 * HTTP response from the KYC provider plugin. 106 */ 107 struct MHD_Response *proof_response; 108 109 /** 110 * HTTP response code from the KYC provider plugin. 111 */ 112 unsigned int proof_response_code; 113 114 /** 115 * Provider configuration section name of the logic we are running. 116 */ 117 const char *provider_name; 118 119 /** 120 * Row in the database for this legitimization operation. 121 */ 122 uint64_t process_row; 123 124 /** 125 * True if we are suspended, 126 */ 127 bool suspended; 128 129 /** 130 * True if @e h_payto is for a wallet. 131 */ 132 bool is_wallet; 133 134 }; 135 136 137 /** 138 * Contexts are kept in a DLL while suspended. 139 */ 140 static struct KycProofContext *kpc_head; 141 142 /** 143 * Contexts are kept in a DLL while suspended. 144 */ 145 static struct KycProofContext *kpc_tail; 146 147 148 /** 149 * Generate HTML error for @a connection using @a template. 150 * 151 * @param connection HTTP client connection 152 * @param template template to expand 153 * @param[in,out] http_status HTTP status of the response 154 * @param ec Taler error code to return 155 * @param message extended message to return 156 * @return MHD response object 157 */ 158 static struct MHD_Response * 159 make_html_error (struct MHD_Connection *connection, 160 const char *template, 161 unsigned int *http_status, 162 enum TALER_ErrorCode ec, 163 const char *message) 164 { 165 struct MHD_Response *response = NULL; 166 json_t *body; 167 enum GNUNET_GenericReturnValue ret; 168 169 body = GNUNET_JSON_PACK ( 170 GNUNET_JSON_pack_allow_null ( 171 GNUNET_JSON_pack_string ("message", 172 message)), 173 TALER_JSON_pack_ec ( 174 ec)); 175 ret = TALER_TEMPLATING_build (connection, 176 http_status, 177 template, 178 NULL, 179 NULL, 180 body, 181 &response); 182 GNUNET_break (GNUNET_SYSERR != ret); 183 if (GNUNET_SYSERR != ret) 184 GNUNET_break (MHD_NO != 185 MHD_add_response_header (response, 186 MHD_HTTP_HEADER_CONTENT_TYPE, 187 "text/html")); 188 189 json_decref (body); 190 return response; 191 } 192 193 194 /** 195 * Resume processing the @a kpc request. 196 * 197 * @param kpc request to resume 198 */ 199 static void 200 kpc_resume (struct KycProofContext *kpc) 201 { 202 GNUNET_assert (GNUNET_YES == kpc->suspended); 203 kpc->suspended = false; 204 GNUNET_CONTAINER_DLL_remove (kpc_head, 205 kpc_tail, 206 kpc); 207 MHD_resume_connection (kpc->rc->connection); 208 TALER_MHD_daemon_trigger (); 209 } 210 211 212 void 213 TEH_kyc_proof_cleanup (void) 214 { 215 struct KycProofContext *kpc; 216 217 while (NULL != (kpc = kpc_head)) 218 { 219 if (NULL != kpc->ph) 220 { 221 kpc->logic->proof_cancel (kpc->ph); 222 kpc->ph = NULL; 223 } 224 kpc_resume (kpc); 225 } 226 } 227 228 229 /** 230 * Function called after the KYC-AML trigger is done. 231 * 232 * @param cls closure 233 * @param ec error code or 0 on success 234 * @param detail error message or NULL on success / no info 235 */ 236 static void 237 proof_finish ( 238 void *cls, 239 enum TALER_ErrorCode ec, 240 const char *detail) 241 { 242 struct KycProofContext *kpc = cls; 243 244 kpc->kat = NULL; 245 if (TALER_EC_NONE != ec) 246 { 247 kpc->response_code = TALER_ErrorCode_get_http_status (ec); 248 GNUNET_break (5 != kpc->response_code / 100); 249 GNUNET_assert (kpc->response_code != UINT_MAX); 250 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 251 "Templating error response for %d and HTTP status %u (%s)\n", 252 (int) ec, 253 kpc->response_code, 254 detail); 255 kpc->response = make_html_error ( 256 kpc->rc->connection, 257 "kyc-proof-internal-error", 258 &kpc->response_code, 259 ec, 260 detail); 261 } 262 else 263 { 264 GNUNET_assert (NULL != kpc->proof_response); 265 kpc->response_code = kpc->proof_response_code; 266 kpc->response = kpc->proof_response; 267 kpc->proof_response = NULL; 268 kpc->proof_response_code = 0; 269 } 270 GNUNET_assert (NULL != kpc->response); 271 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 272 "Resuming with response %p and status %u\n", 273 kpc->response, 274 kpc->response_code); 275 kpc_resume (kpc); 276 } 277 278 279 /** 280 * Respond with an HTML message on the given @a rc. 281 * 282 * @param[in,out] rc request to respond to 283 * @param http_status HTTP status code to use 284 * @param template template to fill in 285 * @param ec error code to use for the template 286 * @param message additional message to return 287 * @return MHD result code 288 */ 289 static MHD_RESULT 290 respond_html_ec (struct TEH_RequestContext *rc, 291 unsigned int http_status, 292 const char *template, 293 enum TALER_ErrorCode ec, 294 const char *message) 295 { 296 struct MHD_Response *response; 297 MHD_RESULT res; 298 299 response = make_html_error (rc->connection, 300 template, 301 &http_status, 302 ec, 303 message); 304 res = MHD_queue_response (rc->connection, 305 http_status, 306 response); 307 MHD_destroy_response (response); 308 return res; 309 } 310 311 312 /** 313 * Function called with the result of a proof check operation. 314 * 315 * Note that the "decref" for the @a response 316 * will be done by the callee and MUST NOT be done by the plugin. 317 * 318 * @param cls closure 319 * @param status KYC status 320 * @param provider_name name of the provider 321 * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown 322 * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown 323 * @param expiration until when is the KYC check valid 324 * @param attributes user attributes returned by the provider 325 * @param http_status HTTP status code of @a response 326 * @param[in] response to return to the HTTP client 327 */ 328 static void 329 proof_cb ( 330 void *cls, 331 enum TALER_KYCLOGIC_KycStatus status, 332 const char *provider_name, 333 const char *provider_user_id, 334 const char *provider_legitimization_id, 335 struct GNUNET_TIME_Absolute expiration, 336 const json_t *attributes, 337 unsigned int http_status, 338 struct MHD_Response *response) 339 { 340 struct KycProofContext *kpc = cls; 341 struct TEH_RequestContext *rc = kpc->rc; 342 struct GNUNET_AsyncScopeSave old_scope; 343 enum GNUNET_DB_QueryStatus qs; 344 345 kpc->ph = NULL; 346 kpc->proof_response = response; 347 kpc->proof_response_code = http_status; 348 GNUNET_async_scope_enter (&rc->async_scope_id, 349 &old_scope); 350 switch (status) 351 { 352 case TALER_KYCLOGIC_STATUS_SUCCESS: 353 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 354 "KYC process #%llu succeeded with KYC provider\n", 355 (unsigned long long) kpc->process_row); 356 qs = TEH_kyc_store_attributes ( 357 kpc->process_row, 358 &kpc->h_payto, 359 provider_name, 360 provider_user_id, 361 provider_legitimization_id, 362 expiration, 363 attributes); 364 if (0 >= qs) 365 { 366 GNUNET_break (0); 367 proof_finish (kpc, 368 TALER_EC_GENERIC_DB_STORE_FAILED, 369 "kyc_store_attributes"); 370 break; 371 } 372 373 kpc->kat = TEH_kyc_run_measure_for_attributes ( 374 &rc->async_scope_id, 375 kpc->process_row, 376 &kpc->h_payto, 377 kpc->is_wallet, 378 &proof_finish, 379 kpc); 380 if (NULL == kpc->kat) 381 { 382 GNUNET_break_op (0); 383 proof_finish (kpc, 384 TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN, 385 NULL); 386 } 387 break; 388 case TALER_KYCLOGIC_STATUS_FAILED: 389 case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED: 390 case TALER_KYCLOGIC_STATUS_USER_ABORTED: 391 case TALER_KYCLOGIC_STATUS_ABORTED: 392 GNUNET_assert (NULL == kpc->kat); 393 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 394 "KYC process %s/%s (Row #%llu) failed: %d\n", 395 provider_user_id, 396 provider_legitimization_id, 397 (unsigned long long) kpc->process_row, 398 status); 399 if (5 == http_status / 100) 400 { 401 char *msg; 402 403 /* OAuth2 server had a problem, do NOT log this as a KYC failure */ 404 GNUNET_break (0); 405 GNUNET_asprintf (&msg, 406 "Failure by KYC provider (HTTP status %u)\n", 407 http_status); 408 proof_finish ( 409 kpc, 410 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY, 411 msg); 412 GNUNET_free (msg); 413 break; 414 } 415 if (! TEH_kyc_failed ( 416 kpc->process_row, 417 &kpc->h_payto, 418 kpc->provider_name, 419 provider_user_id, 420 provider_legitimization_id, 421 TALER_KYCLOGIC_status2s (status), 422 TALER_EC_EXCHANGE_GENERIC_KYC_FAILED)) 423 { 424 GNUNET_break (0); 425 proof_finish ( 426 kpc, 427 TALER_EC_GENERIC_DB_STORE_FAILED, 428 "TEH_kyc_failed"); 429 break; 430 } 431 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 432 "KYC process #%llu failed with status %d\n", 433 (unsigned long long) kpc->process_row, 434 status); 435 proof_finish (kpc, 436 TALER_EC_NONE, 437 NULL); 438 break; 439 default: 440 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 441 "KYC status of %s/%s (Row #%llu) is %d\n", 442 provider_user_id, 443 provider_legitimization_id, 444 (unsigned long long) kpc->process_row, 445 (int) status); 446 break; 447 } 448 GNUNET_async_scope_restore (&old_scope); 449 } 450 451 452 /** 453 * Function called to clean up a context. 454 * 455 * @param rc request context 456 */ 457 static void 458 clean_kpc (struct TEH_RequestContext *rc) 459 { 460 struct KycProofContext *kpc = rc->rh_ctx; 461 462 if (NULL != kpc->ph) 463 { 464 kpc->logic->proof_cancel (kpc->ph); 465 kpc->ph = NULL; 466 } 467 if (NULL != kpc->kat) 468 { 469 TEH_kyc_run_measure_cancel (kpc->kat); 470 kpc->kat = NULL; 471 } 472 if (NULL != kpc->response) 473 { 474 MHD_destroy_response (kpc->response); 475 kpc->response = NULL; 476 } 477 if (NULL != kpc->proof_response) 478 { 479 MHD_destroy_response (kpc->proof_response); 480 kpc->proof_response = NULL; 481 } 482 GNUNET_free (kpc->provider_user_id); 483 GNUNET_free (kpc->provider_legitimization_id); 484 GNUNET_free (kpc); 485 } 486 487 488 MHD_RESULT 489 TEH_handler_kyc_proof ( 490 struct TEH_RequestContext *rc, 491 const char *const args[1]) 492 { 493 struct KycProofContext *kpc = rc->rh_ctx; 494 const char *provider_name_or_logic = args[0]; 495 496 if (NULL == kpc) 497 { 498 /* first time */ 499 if (NULL == provider_name_or_logic) 500 { 501 GNUNET_break_op (0); 502 return respond_html_ec ( 503 rc, 504 MHD_HTTP_NOT_FOUND, 505 "kyc-proof-endpoint-unknown", 506 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 507 "'/kyc-proof/$PROVIDER_NAME?state=$H_PAYTO' required"); 508 } 509 kpc = GNUNET_new (struct KycProofContext); 510 kpc->rc = rc; 511 rc->rh_ctx = kpc; 512 rc->rh_cleaner = &clean_kpc; 513 TALER_MHD_parse_request_arg_auto_t (rc->connection, 514 "state", 515 &kpc->h_payto); 516 if (GNUNET_OK != 517 TALER_KYCLOGIC_lookup_logic ( 518 provider_name_or_logic, 519 &kpc->logic, 520 &kpc->pd, 521 &kpc->provider_name)) 522 { 523 GNUNET_break_op (0); 524 return respond_html_ec ( 525 rc, 526 MHD_HTTP_NOT_FOUND, 527 "kyc-proof-target-unknown", 528 TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, 529 provider_name_or_logic); 530 } 531 if (NULL != kpc->provider_name) 532 { 533 enum GNUNET_DB_QueryStatus qs; 534 struct GNUNET_TIME_Absolute expiration; 535 536 if (0 != strcmp (provider_name_or_logic, 537 kpc->provider_name)) 538 { 539 GNUNET_break_op (0); 540 return respond_html_ec ( 541 rc, 542 MHD_HTTP_BAD_REQUEST, 543 "kyc-proof-bad-request", 544 TALER_EC_GENERIC_PARAMETER_MALFORMED, 545 "PROVIDER_NAME"); 546 } 547 548 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 549 "Looking for KYC process at %s\n", 550 kpc->provider_name); 551 qs = TEH_plugin->lookup_kyc_process_by_account ( 552 TEH_plugin->cls, 553 kpc->provider_name, 554 &kpc->h_payto, 555 &kpc->process_row, 556 &expiration, 557 &kpc->provider_user_id, 558 &kpc->provider_legitimization_id, 559 &kpc->is_wallet); 560 switch (qs) 561 { 562 case GNUNET_DB_STATUS_HARD_ERROR: 563 case GNUNET_DB_STATUS_SOFT_ERROR: 564 GNUNET_break (0); 565 return respond_html_ec ( 566 rc, 567 MHD_HTTP_INTERNAL_SERVER_ERROR, 568 "kyc-proof-internal-error", 569 TALER_EC_GENERIC_DB_FETCH_FAILED, 570 "lookup_kyc_process_by_account"); 571 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 572 GNUNET_break_op (0); 573 return respond_html_ec ( 574 rc, 575 MHD_HTTP_NOT_FOUND, 576 "kyc-proof-target-unknown", 577 TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN, 578 kpc->provider_name); 579 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 580 break; 581 } 582 if (GNUNET_TIME_absolute_is_future (expiration)) 583 { 584 /* KYC not required */ 585 return respond_html_ec ( 586 rc, 587 MHD_HTTP_OK, 588 "kyc-proof-already-done", 589 TALER_EC_NONE, 590 NULL); 591 } 592 } 593 kpc->ph = kpc->logic->proof ( 594 kpc->logic->cls, 595 kpc->pd, 596 rc->connection, 597 &kpc->h_payto, 598 kpc->process_row, 599 kpc->provider_user_id, 600 kpc->provider_legitimization_id, 601 &proof_cb, 602 kpc); 603 if (NULL == kpc->ph) 604 { 605 GNUNET_break (0); 606 return respond_html_ec ( 607 rc, 608 MHD_HTTP_INTERNAL_SERVER_ERROR, 609 "kyc-proof-internal-error", 610 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 611 "could not start proof with KYC logic"); 612 } 613 614 615 kpc->suspended = true; 616 GNUNET_CONTAINER_DLL_insert (kpc_head, 617 kpc_tail, 618 kpc); 619 MHD_suspend_connection (rc->connection); 620 return MHD_YES; 621 } 622 623 if (NULL == kpc->response) 624 { 625 GNUNET_break (0); 626 return respond_html_ec ( 627 rc, 628 MHD_HTTP_INTERNAL_SERVER_ERROR, 629 "kyc-proof-internal-error", 630 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 631 "handler resumed without response"); 632 } 633 634 /* return response from KYC logic */ 635 return MHD_queue_response (rc->connection, 636 kpc->response_code, 637 kpc->response); 638 } 639 640 641 /* end of taler-exchange-httpd_kyc-proof.c */