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