taler-exchange-httpd_post-kyc-upload-ID.c (16361B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025 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_post-kyc-upload-ID.c 18 * @brief Handle /kyc-upload/$ID request 19 * @author Christian Grothoff 20 */ 21 #include "taler-exchange-httpd_common_kyc.h" 22 #include "taler-exchange-httpd_post-kyc-upload-ID.h" 23 #include "exchange-database/preflight.h" 24 #include "exchange-database/insert_legitimization_process.h" 25 #include "exchange-database/get_completed_legitimization.h" 26 #include "exchange-database/get_pending_legitimization.h" 27 28 #define DEBUG 0 29 30 #define MAX_RETRIES 3 31 32 /** 33 * Context used for processing the KYC upload req 34 */ 35 struct UploadContext 36 { 37 38 /** 39 * Kept in a DLL. 40 */ 41 struct UploadContext *next; 42 43 /** 44 * Kept in a DLL. 45 */ 46 struct UploadContext *prev; 47 48 /** 49 * Access token for the KYC data of the account. 50 */ 51 struct TALER_AccountAccessTokenP access_token; 52 53 /** 54 * Authorization hash for the selected measure. 55 */ 56 struct TALER_KycMeasureAuthorizationHashP shv; 57 58 /** 59 * Index of the measure this upload is for. 60 */ 61 unsigned int measure_index; 62 63 /** 64 * HTTP status code to use with @e response. 65 */ 66 unsigned int response_code; 67 68 /** 69 * Index in the legitimization measures table this ID 70 * refers to. 71 */ 72 unsigned long long legitimization_measure_serial_id; 73 74 /** 75 * Response to return, NULL if none yet. 76 */ 77 struct MHD_Response *response; 78 79 /** 80 * Request we are processing. 81 */ 82 struct TEH_RequestContext *rc; 83 84 /** 85 * Handle for async KYC processing. 86 */ 87 struct TEH_KycMeasureRunContext *kat; 88 89 /** 90 * Uploaded data, in JSON. 91 */ 92 const json_t *result; 93 94 /** 95 * Set by the transaction to the legitimization process row. 96 */ 97 uint64_t legi_process_row; 98 99 /** 100 * Set by the transaction to the affected account payto hash. 101 */ 102 struct TALER_NormalizedPaytoHashP h_payto; 103 104 /** 105 * Set by the transaction to true if the account is for a wallet. 106 */ 107 bool is_wallet; 108 109 }; 110 111 112 /** 113 * Kept in a DLL. 114 */ 115 static struct UploadContext *uc_head; 116 117 /** 118 * Kept in a DLL. 119 */ 120 static struct UploadContext *uc_tail; 121 122 123 void 124 TEH_kyc_upload_cleanup () 125 { 126 struct UploadContext *uc; 127 128 while (NULL != (uc = uc_head)) 129 { 130 MHD_resume_connection (uc->rc->connection); 131 GNUNET_CONTAINER_DLL_remove (uc_head, 132 uc_tail, 133 uc); 134 } 135 } 136 137 138 /** 139 * Function called to clean up upload context. 140 * 141 * @param[in,out] rc context to clean up 142 */ 143 static void 144 upload_cleaner (struct TEH_RequestContext *rc) 145 { 146 struct UploadContext *uc = rc->rh_ctx; 147 148 if (NULL != uc->kat) 149 { 150 TEH_kyc_run_measure_cancel (uc->kat); 151 uc->kat = NULL; 152 } 153 if (NULL != uc->response) 154 { 155 MHD_destroy_response (uc->response); 156 uc->response = NULL; 157 } 158 GNUNET_free (uc); 159 } 160 161 162 /** 163 * Function called after the KYC-AML trigger is done. 164 * 165 * @param cls closure 166 * @param ec error code or 0 on success 167 * @param detail error message or NULL on success / no info 168 */ 169 static void 170 aml_trigger_callback ( 171 void *cls, 172 enum TALER_ErrorCode ec, 173 const char *detail) 174 { 175 struct UploadContext *uc = cls; 176 177 uc->kat = NULL; 178 GNUNET_assert (NULL == uc->response); 179 if (TALER_EC_NONE != ec) 180 { 181 uc->response_code = TALER_ErrorCode_get_http_status (ec); 182 if (0 == uc->response_code) 183 { 184 GNUNET_break (0); 185 uc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 186 } 187 GNUNET_assert (uc->response_code != UINT_MAX); 188 uc->response = TALER_MHD_make_error ( 189 ec, 190 detail); 191 } 192 else 193 { 194 uc->response_code = MHD_HTTP_NO_CONTENT; 195 uc->response = MHD_create_response_from_buffer_static ( 196 0, 197 "" 198 ); 199 TALER_MHD_add_global_headers (uc->response, 200 true); 201 } 202 203 MHD_resume_connection (uc->rc->connection); 204 GNUNET_CONTAINER_DLL_remove (uc_head, 205 uc_tail, 206 uc); 207 TALER_MHD_daemon_trigger (); 208 } 209 210 211 /** 212 * Do the main database transaction. 213 * 214 * @param cls closure with a `struct UploadContext` 215 * @param connection MHD request which triggered the transaction 216 * @param[out] mhd_ret set to MHD response status for @a connection, 217 * if transaction failed (!) 218 */ 219 static enum GNUNET_DB_QueryStatus 220 transact (void *cls, 221 struct MHD_Connection *connection, 222 enum MHD_Result *mhd_ret) 223 { 224 struct UploadContext *uc = cls; 225 struct TEH_RequestContext *rc = uc->rc; 226 enum GNUNET_DB_QueryStatus qs; 227 json_t *jmeasures; 228 bool is_finished = false; 229 size_t enc_attributes_len; 230 void *enc_attributes; 231 const char *error_message; 232 char *form_name; 233 enum TALER_ErrorCode ec; 234 235 qs = TALER_EXCHANGEDB_get_completed_legitimization ( 236 TEH_pg, 237 uc->legitimization_measure_serial_id, 238 uc->measure_index, 239 &uc->access_token, 240 &uc->h_payto, 241 &uc->is_wallet, 242 &jmeasures, 243 &is_finished, 244 &enc_attributes_len, 245 &enc_attributes); 246 /* FIXME: not exactly performant/elegant, should eventually 247 modify lookup_completed_legitimization to 248 return something if we are purely pending? */ 249 switch (qs) 250 { 251 case GNUNET_DB_STATUS_HARD_ERROR: 252 GNUNET_break (0); 253 *mhd_ret = TALER_MHD_reply_with_error ( 254 rc->connection, 255 MHD_HTTP_INTERNAL_SERVER_ERROR, 256 TALER_EC_GENERIC_DB_FETCH_FAILED, 257 "lookup_completed_legitimization"); 258 return qs; 259 case GNUNET_DB_STATUS_SOFT_ERROR: 260 return qs; 261 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 262 qs = TALER_EXCHANGEDB_get_pending_legitimization ( 263 TEH_pg, 264 uc->legitimization_measure_serial_id, 265 &uc->access_token, 266 &uc->h_payto, 267 &jmeasures, 268 &is_finished, 269 &uc->is_wallet); 270 switch (qs) 271 { 272 case GNUNET_DB_STATUS_HARD_ERROR: 273 GNUNET_break (0); 274 *mhd_ret = TALER_MHD_reply_with_error ( 275 rc->connection, 276 MHD_HTTP_INTERNAL_SERVER_ERROR, 277 TALER_EC_GENERIC_DB_FETCH_FAILED, 278 "lookup_pending_legitimization"); 279 return qs; 280 case GNUNET_DB_STATUS_SOFT_ERROR: 281 return qs; 282 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 283 GNUNET_break_op (0); 284 *mhd_ret = TALER_MHD_reply_with_error ( 285 rc->connection, 286 MHD_HTTP_NOT_FOUND, 287 TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN, 288 NULL); 289 return GNUNET_DB_STATUS_HARD_ERROR; 290 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 291 break; 292 } 293 break; 294 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 295 break; 296 } 297 298 { 299 struct TALER_KycMeasureAuthorizationHashP shv; 300 301 TALER_kyc_measure_authorization_hash ( 302 &uc->access_token, 303 uc->legitimization_measure_serial_id, 304 uc->measure_index, 305 &shv); 306 if (0 != 307 GNUNET_memcmp (&uc->shv, 308 &shv)) 309 { 310 GNUNET_break_op (0); 311 GNUNET_free (enc_attributes); 312 json_decref (jmeasures); 313 *mhd_ret = TALER_MHD_reply_with_error ( 314 rc->connection, 315 MHD_HTTP_NOT_FOUND, 316 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 317 rc->url); 318 return GNUNET_DB_STATUS_HARD_ERROR; 319 } 320 } 321 322 if (NULL != enc_attributes) 323 { 324 json_t *xattributes; 325 326 xattributes 327 = TALER_CRYPTO_kyc_attributes_decrypt ( 328 &TEH_attribute_key, 329 enc_attributes, 330 enc_attributes_len); 331 if (json_equal (xattributes, 332 uc->result)) 333 { 334 /* Request is idempotent! */ 335 json_decref (xattributes); 336 GNUNET_free (enc_attributes); 337 json_decref (jmeasures); 338 if (is_finished) 339 { 340 *mhd_ret = TALER_MHD_reply_static ( 341 rc->connection, 342 MHD_HTTP_NO_CONTENT, 343 NULL, 344 NULL, 345 0); 346 return GNUNET_DB_STATUS_HARD_ERROR; 347 } 348 349 /* Note: problem below is not here, but likely some previous 350 upload of the attributes failed badly in an AML program. */ 351 GNUNET_break (0); 352 *mhd_ret = TALER_MHD_reply_with_error ( 353 rc->connection, 354 MHD_HTTP_INTERNAL_SERVER_ERROR, 355 TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG, 356 "attributes known, but legitimization process failed"); 357 return GNUNET_DB_STATUS_HARD_ERROR; 358 } 359 json_decref (xattributes); 360 GNUNET_free (enc_attributes); 361 json_decref (jmeasures); 362 /* Form was already done with with different attributes, conflict! */ 363 GNUNET_break_op (0); 364 *mhd_ret = TALER_MHD_reply_with_error ( 365 rc->connection, 366 MHD_HTTP_CONFLICT, 367 TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED, 368 NULL); 369 return GNUNET_DB_STATUS_HARD_ERROR; 370 } 371 if (is_finished) 372 { 373 /* This should not be possible (is_finished but NULL==enc_attributes), 374 but also we should not run logic again if we are finished. */ 375 GNUNET_break_op (0); 376 json_decref (jmeasures); 377 *mhd_ret = TALER_MHD_reply_with_error ( 378 rc->connection, 379 MHD_HTTP_CONFLICT, 380 TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED, 381 NULL); 382 return GNUNET_DB_STATUS_HARD_ERROR; 383 } 384 ec = TALER_KYCLOGIC_check_form (jmeasures, 385 uc->measure_index, 386 uc->result, 387 &form_name, 388 &error_message); 389 if (TALER_EC_NONE != ec) 390 { 391 GNUNET_break_op (0); 392 json_decref (jmeasures); 393 *mhd_ret = TALER_MHD_reply_with_ec ( 394 rc->connection, 395 ec, 396 error_message); 397 return GNUNET_DB_STATUS_HARD_ERROR; 398 } 399 json_decref (jmeasures); 400 401 /* Setup KYC process (which we will then immediately 'finish') */ 402 qs = TALER_EXCHANGEDB_insert_legitimization_process ( 403 TEH_pg, 404 &uc->h_payto, 405 uc->measure_index, 406 uc->legitimization_measure_serial_id, 407 form_name, 408 NULL, /* provider account ID */ 409 NULL, /* provider legi ID */ 410 &uc->legi_process_row); 411 switch (qs) 412 { 413 case GNUNET_DB_STATUS_HARD_ERROR: 414 GNUNET_break (0); 415 GNUNET_free (form_name); 416 *mhd_ret = TALER_MHD_reply_with_error ( 417 rc->connection, 418 MHD_HTTP_INTERNAL_SERVER_ERROR, 419 TALER_EC_GENERIC_DB_STORE_FAILED, 420 "insert_kyc_requirement_process"); 421 return GNUNET_DB_STATUS_HARD_ERROR; 422 case GNUNET_DB_STATUS_SOFT_ERROR: 423 GNUNET_free (form_name); 424 return qs; 425 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 426 GNUNET_break (0); 427 GNUNET_free (form_name); 428 *mhd_ret = TALER_MHD_reply_with_error ( 429 rc->connection, 430 MHD_HTTP_INTERNAL_SERVER_ERROR, 431 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 432 "insert_kyc_requirement_process"); 433 return GNUNET_DB_STATUS_HARD_ERROR; 434 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 435 break; 436 } 437 { 438 const char *form_id 439 = json_string_value (json_object_get (uc->result, 440 "FORM_ID")); 441 442 GNUNET_assert (NULL != form_id); /* checked earlier */ 443 if (0 != 444 strcmp (form_name, 445 form_id)) 446 { 447 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 448 "Expected form `%s', got `%s'\n", 449 form_name, 450 form_id); 451 *mhd_ret = TALER_MHD_reply_with_error ( 452 rc->connection, 453 MHD_HTTP_CONFLICT, 454 TALER_EC_EXCHANGE_KYC_INVALID_FORM_SUBMITTED, 455 "FORM_ID"); 456 GNUNET_free (form_name); 457 return GNUNET_DB_STATUS_HARD_ERROR; 458 } 459 } 460 qs = TEH_kyc_store_attributes ( 461 uc->legi_process_row, 462 &uc->h_payto, 463 form_name, 464 NULL /* provider account */, 465 NULL /* provider legi ID */, 466 GNUNET_TIME_UNIT_FOREVER_ABS, /* expiration time */ 467 uc->result); 468 GNUNET_free (form_name); 469 switch (qs) 470 { 471 case GNUNET_DB_STATUS_HARD_ERROR: 472 GNUNET_break (0); 473 *mhd_ret = TALER_MHD_reply_with_error ( 474 rc->connection, 475 MHD_HTTP_INTERNAL_SERVER_ERROR, 476 TALER_EC_GENERIC_DB_STORE_FAILED, 477 "kyc_store_attributes"); 478 return GNUNET_DB_STATUS_HARD_ERROR; 479 case GNUNET_DB_STATUS_SOFT_ERROR: 480 return qs; 481 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 482 GNUNET_break (0); 483 *mhd_ret = TALER_MHD_reply_with_error ( 484 rc->connection, 485 MHD_HTTP_INTERNAL_SERVER_ERROR, 486 TALER_EC_GENERIC_DB_STORE_FAILED, 487 "kyc_store_attributes"); 488 return GNUNET_DB_STATUS_HARD_ERROR; 489 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 490 return qs; 491 } 492 GNUNET_assert (0); 493 *mhd_ret = MHD_NO; 494 return GNUNET_DB_STATUS_HARD_ERROR; 495 } 496 497 498 enum MHD_Result 499 TEH_handler_kyc_upload ( 500 struct TEH_RequestContext *rc, 501 const json_t *root, 502 const char *const args[1]) 503 { 504 struct UploadContext *uc = rc->rh_ctx; 505 const char *id = args[0]; 506 507 if (NULL == uc) 508 { 509 const char *slash; 510 char dummy; 511 512 uc = GNUNET_new (struct UploadContext); 513 uc->rc = rc; 514 uc->result = root; 515 rc->rh_ctx = uc; 516 rc->rh_cleaner = &upload_cleaner; 517 slash = strchr (id, '-'); 518 if (NULL == slash) 519 { 520 GNUNET_break_op (0); 521 return TALER_MHD_reply_with_error ( 522 rc->connection, 523 MHD_HTTP_NOT_FOUND, 524 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 525 rc->url); 526 } 527 if (GNUNET_OK != 528 GNUNET_STRINGS_string_to_data (id, 529 slash - id, 530 &uc->shv, 531 sizeof (uc->shv))) 532 { 533 GNUNET_break_op (0); 534 return TALER_MHD_reply_with_error ( 535 rc->connection, 536 MHD_HTTP_BAD_REQUEST, 537 TALER_EC_GENERIC_PARAMETER_MALFORMED, 538 "Authorization hash in ID is malformed"); 539 } 540 if (2 != 541 sscanf (slash + 1, 542 "%u-%llu%c", 543 &uc->measure_index, 544 &uc->legitimization_measure_serial_id, 545 &dummy)) 546 { 547 GNUNET_break_op (0); 548 return TALER_MHD_reply_with_error ( 549 rc->connection, 550 MHD_HTTP_BAD_REQUEST, 551 TALER_EC_GENERIC_PARAMETER_MALFORMED, 552 "ID is malformed"); 553 } 554 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 555 "/kyc-upload received form submission\n"); 556 if ( (NULL != root) && 557 (! json_is_string (json_object_get (root, 558 "FORM_ID"))) ) 559 { 560 GNUNET_break_op (0); 561 return TALER_MHD_reply_with_error ( 562 rc->connection, 563 MHD_HTTP_BAD_REQUEST, 564 TALER_EC_GENERIC_PARAMETER_MALFORMED, 565 "FORM_ID"); 566 } 567 #if DEBUG 568 json_dumpf (root, 569 stderr, 570 JSON_INDENT (2)); 571 fprintf (stderr, 572 "\n"); 573 #endif 574 } 575 if (NULL != uc->response) 576 { 577 return MHD_queue_response (rc->connection, 578 uc->response_code, 579 uc->response); 580 581 } 582 583 if (GNUNET_OK != 584 TALER_EXCHANGEDB_preflight (TEH_pg)) 585 { 586 GNUNET_break (0); 587 return TALER_MHD_reply_with_error ( 588 rc->connection, 589 MHD_HTTP_INTERNAL_SERVER_ERROR, 590 TALER_EC_GENERIC_DB_SETUP_FAILED, 591 NULL); 592 } 593 594 { 595 enum MHD_Result mhd_ret = -1; 596 597 if (GNUNET_OK != 598 TEH_DB_run_transaction (rc->connection, 599 "kyc-upload", 600 TEH_MT_REQUEST_KYC_UPLOAD, 601 &mhd_ret, 602 &transact, 603 uc)) 604 return mhd_ret; 605 } 606 607 uc->kat = TEH_kyc_run_measure_for_attributes ( 608 &rc->async_scope_id, 609 uc->legi_process_row, 610 &uc->h_payto, 611 uc->is_wallet, 612 &aml_trigger_callback, 613 uc); 614 if (NULL == uc->kat) 615 { 616 GNUNET_break (0); 617 return TALER_MHD_reply_with_error ( 618 rc->connection, 619 MHD_HTTP_INTERNAL_SERVER_ERROR, 620 TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG, 621 "TEH_kyc_finished"); 622 } 623 MHD_suspend_connection (uc->rc->connection); 624 GNUNET_CONTAINER_DLL_insert (uc_head, 625 uc_tail, 626 uc); 627 return MHD_YES; 628 }