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