taler-exchange-httpd_kyc-start.c (13865B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2021-2024 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-start.c 18 * @brief Handle request for starting a KYC process with an external provider. 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 <pthread.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_signatures.h" 31 #include "taler/taler_dbevents.h" 32 #include "taler-exchange-httpd_keys.h" 33 #include "taler-exchange-httpd_kyc-start.h" 34 #include "taler-exchange-httpd_responses.h" 35 36 37 /** 38 * POST request in asynchronous processing. 39 */ 40 struct KycPoller 41 { 42 43 /** 44 * Access token for the KYC data of the account. 45 */ 46 struct TALER_AccountAccessTokenP access_token; 47 48 /** 49 * Authorization hash for the selected measure. 50 */ 51 struct TALER_KycMeasureAuthorizationHashP shv; 52 53 /** 54 * Hash of the payto:// URI we are starting to the KYC for. 55 */ 56 struct TALER_NormalizedPaytoHashP h_payto; 57 58 /** 59 * Kept in a DLL. 60 */ 61 struct KycPoller *next; 62 63 /** 64 * Kept in a DLL. 65 */ 66 struct KycPoller *prev; 67 68 /** 69 * Connection we are handling. 70 */ 71 struct MHD_Connection *connection; 72 73 /** 74 * Logic for @e ih 75 */ 76 struct TALER_KYCLOGIC_Plugin *ih_logic; 77 78 /** 79 * Handle to asynchronously running KYC initiation 80 * request. 81 */ 82 struct TALER_KYCLOGIC_InitiateHandle *ih; 83 84 /** 85 * Set of applicable KYC measures. 86 */ 87 json_t *jmeasures; 88 89 /** 90 * Where to redirect the user to start the KYC process. 91 */ 92 char *redirect_url; 93 94 /** 95 * Set to the name of the KYC provider. 96 */ 97 const char *provider_name; 98 99 /** 100 * Set to error details, on error (@ec not TALER_EC_NONE). 101 */ 102 char *hint; 103 104 /** 105 * Row of the requirement being started. 106 */ 107 unsigned long long legitimization_measure_serial_id; 108 109 /** 110 * Row of KYC process being initiated. 111 */ 112 uint64_t process_row; 113 114 /** 115 * Index of the measure this upload is for. 116 */ 117 unsigned int measure_index; 118 119 /** 120 * Set to error encountered with KYC logic, if any. 121 */ 122 enum TALER_ErrorCode ec; 123 124 /** 125 * True if we are still 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 * Head of list of requests in asynchronous processing. 139 */ 140 static struct KycPoller *kyp_head; 141 142 /** 143 * Tail of list of requests in asynchronous processing. 144 */ 145 static struct KycPoller *kyp_tail; 146 147 148 void 149 TEH_kyc_start_cleanup () 150 { 151 struct KycPoller *kyp; 152 153 while (NULL != (kyp = kyp_head)) 154 { 155 GNUNET_CONTAINER_DLL_remove (kyp_head, 156 kyp_tail, 157 kyp); 158 if (NULL != kyp->ih) 159 { 160 kyp->ih_logic->initiate_cancel (kyp->ih); 161 kyp->ih = NULL; 162 } 163 if (kyp->suspended) 164 { 165 kyp->suspended = false; 166 MHD_resume_connection (kyp->connection); 167 } 168 } 169 } 170 171 172 /** 173 * Function called once a connection is done to 174 * clean up the `struct ReservePoller` state. 175 * 176 * @param rc context to clean up for 177 */ 178 static void 179 kyp_cleanup (struct TEH_RequestContext *rc) 180 { 181 struct KycPoller *kyp = rc->rh_ctx; 182 183 GNUNET_assert (! kyp->suspended); 184 if (NULL != kyp->ih) 185 { 186 kyp->ih_logic->initiate_cancel (kyp->ih); 187 kyp->ih = NULL; 188 } 189 GNUNET_free (kyp->redirect_url); 190 GNUNET_free (kyp->hint); 191 json_decref (kyp->jmeasures); 192 GNUNET_free (kyp); 193 } 194 195 196 /** 197 * Function called with the result of a KYC initiation 198 * operation. 199 * 200 * @param cls closure with our `struct KycPoller *` 201 * @param ec #TALER_EC_NONE on success 202 * @param redirect_url set to where to redirect the user on success, NULL on failure 203 * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown 204 * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown 205 * @param error_msg_hint set to additional details to return to user, NULL on success 206 */ 207 static void 208 initiate_cb ( 209 void *cls, 210 enum TALER_ErrorCode ec, 211 const char *redirect_url, 212 const char *provider_user_id, 213 const char *provider_legitimization_id, 214 const char *error_msg_hint) 215 { 216 struct KycPoller *kyp = cls; 217 enum GNUNET_DB_QueryStatus qs; 218 219 kyp->ih = NULL; 220 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 221 "KYC initiation `%s' completed with ec=%d (%s)\n", 222 provider_legitimization_id, 223 ec, 224 (TALER_EC_NONE == ec) 225 ? redirect_url 226 : error_msg_hint); 227 kyp->ec = ec; 228 if (TALER_EC_NONE == ec) 229 { 230 kyp->redirect_url = GNUNET_strdup (redirect_url); 231 } 232 else 233 { 234 kyp->hint = GNUNET_strdup (error_msg_hint); 235 } 236 qs = TEH_plugin->update_kyc_process_by_row ( 237 TEH_plugin->cls, 238 kyp->process_row, 239 kyp->provider_name, 240 &kyp->h_payto, 241 provider_user_id, 242 provider_legitimization_id, 243 redirect_url, 244 GNUNET_TIME_UNIT_ZERO_ABS, 245 ec, 246 error_msg_hint, 247 TALER_EC_NONE != ec); 248 if (qs <= 0) 249 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 250 "KYC requirement update failed for %s with status %d at %s:%u\n", 251 TALER_B2S (&kyp->h_payto), 252 qs, 253 __FILE__, 254 __LINE__); 255 GNUNET_assert (kyp->suspended); 256 kyp->suspended = false; 257 GNUNET_CONTAINER_DLL_remove (kyp_head, 258 kyp_tail, 259 kyp); 260 MHD_resume_connection (kyp->connection); 261 TALER_MHD_daemon_trigger (); 262 } 263 264 265 MHD_RESULT 266 TEH_handler_kyc_start ( 267 struct TEH_RequestContext *rc, 268 const json_t *root, 269 const char *const args[1]) 270 { 271 struct KycPoller *kyp = rc->rh_ctx; 272 273 (void) root; 274 if (NULL == kyp) 275 { 276 const char *id = args[0]; 277 enum GNUNET_DB_QueryStatus qs; 278 const struct TALER_KYCLOGIC_KycProvider *provider; 279 struct TALER_KYCLOGIC_ProviderDetails *pd; 280 bool is_finished; 281 const json_t *context; 282 283 kyp = GNUNET_new (struct KycPoller); 284 kyp->connection = rc->connection; 285 rc->rh_ctx = kyp; 286 rc->rh_cleaner = &kyp_cleanup; 287 288 { 289 char dummy; 290 const char *slash; 291 292 slash = strchr (id, '-'); 293 if (NULL == slash) 294 { 295 GNUNET_break_op (0); 296 return TALER_MHD_reply_with_error ( 297 rc->connection, 298 MHD_HTTP_NOT_FOUND, 299 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 300 rc->url); 301 } 302 if (GNUNET_OK != 303 GNUNET_STRINGS_string_to_data (id, 304 slash - id, 305 &kyp->shv, 306 sizeof (kyp->shv))) 307 { 308 GNUNET_break_op (0); 309 return TALER_MHD_reply_with_error ( 310 rc->connection, 311 MHD_HTTP_BAD_REQUEST, 312 TALER_EC_GENERIC_PARAMETER_MALFORMED, 313 "Authorization hash in ID is malformed"); 314 } 315 if (2 != 316 sscanf (slash + 1, 317 "%u-%llu%c", 318 &kyp->measure_index, 319 &kyp->legitimization_measure_serial_id, 320 &dummy)) 321 { 322 GNUNET_break_op (0); 323 return TALER_MHD_reply_with_error ( 324 rc->connection, 325 MHD_HTTP_BAD_REQUEST, 326 TALER_EC_GENERIC_PARAMETER_MALFORMED, 327 "ID is malformed"); 328 } 329 } 330 qs = TEH_plugin->lookup_pending_legitimization ( 331 TEH_plugin->cls, 332 kyp->legitimization_measure_serial_id, 333 &kyp->access_token, 334 &kyp->h_payto, 335 &kyp->jmeasures, 336 &is_finished, 337 &kyp->is_wallet); 338 if (qs < 0) 339 { 340 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); 341 return TALER_MHD_reply_with_ec ( 342 rc->connection, 343 TALER_EC_GENERIC_DB_FETCH_FAILED, 344 "lookup_pending_legitimization"); 345 } 346 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 347 { 348 GNUNET_break_op (0); 349 return TALER_MHD_reply_with_error ( 350 rc->connection, 351 MHD_HTTP_NOT_FOUND, 352 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 353 rc->url); 354 } 355 if (is_finished) 356 { 357 GNUNET_break_op (0); 358 return TALER_MHD_reply_with_error ( 359 rc->connection, 360 MHD_HTTP_CONFLICT, 361 TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED, 362 rc->url); 363 } 364 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 365 "Found pending legitimization %llu with measures\n", 366 (unsigned long long) kyp->legitimization_measure_serial_id); 367 json_dumpf (kyp->jmeasures, 368 stderr, 369 JSON_INDENT (2)); 370 fprintf (stderr, 371 "\n"); 372 373 { 374 struct TALER_KycMeasureAuthorizationHashP shv2; 375 376 TALER_kyc_measure_authorization_hash ( 377 &kyp->access_token, 378 kyp->legitimization_measure_serial_id, 379 kyp->measure_index, 380 &shv2); 381 if (0 != 382 GNUNET_memcmp (&kyp->shv, 383 &shv2)) 384 { 385 GNUNET_break_op (0); 386 return TALER_MHD_reply_with_error ( 387 rc->connection, 388 MHD_HTTP_NOT_FOUND, 389 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 390 rc->url); 391 } 392 } 393 394 { 395 const char *check_name; 396 const char *prog_name; 397 398 kyp->ec = TALER_KYCLOGIC_select_measure ( 399 kyp->jmeasures, 400 kyp->measure_index, 401 &check_name, 402 &prog_name, 403 &context); 404 if (TALER_EC_NONE != kyp->ec) 405 { 406 /* return EC in next call to this function */ 407 GNUNET_break_op (0); 408 kyp->hint 409 = GNUNET_strdup ("TALER_KYCLOGIC_select_measure"); 410 return MHD_YES; 411 } 412 413 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 414 "Selected measure is %llu with check `%s'\n", 415 (unsigned long long) kyp->measure_index, 416 check_name); 417 if (NULL != context) 418 { 419 json_dumpf (context, 420 stderr, 421 JSON_INDENT (2)); 422 fprintf (stderr, 423 "\n"); 424 } 425 426 provider = TALER_KYCLOGIC_check_to_provider ( 427 check_name); 428 if (NULL == provider) 429 { 430 GNUNET_break_op (0); 431 return TALER_MHD_reply_with_error ( 432 rc->connection, 433 MHD_HTTP_CONFLICT, 434 TALER_EC_EXCHANGE_KYC_INVALID_LOGIC_TO_CHECK, 435 check_name); 436 } 437 } 438 439 TALER_KYCLOGIC_provider_to_logic ( 440 provider, 441 &kyp->ih_logic, 442 &pd, 443 &kyp->provider_name); 444 445 /* FIXME-#9419: the next two DB interactions should be ONE 446 transaction */ 447 /* Check if we already initiated this process */ 448 qs = TEH_plugin->get_pending_kyc_requirement_process ( 449 TEH_plugin->cls, 450 &kyp->h_payto, 451 kyp->provider_name, 452 &kyp->redirect_url); 453 if (qs < 0) 454 { 455 GNUNET_break (0); 456 /* Simple query, never should be a soft error. */ 457 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 458 return TALER_MHD_reply_with_error ( 459 rc->connection, 460 MHD_HTTP_INTERNAL_SERVER_ERROR, 461 TALER_EC_GENERIC_DB_FETCH_FAILED, 462 "get_pending_kyc_requirement_process"); 463 } 464 if (NULL != kyp->redirect_url) 465 return MHD_YES; /* success, return the redirect URL 466 (in next call to this function) */ 467 468 /* set up new requirement process */ 469 qs = TEH_plugin->insert_kyc_requirement_process ( 470 TEH_plugin->cls, 471 &kyp->h_payto, 472 kyp->measure_index, 473 kyp->legitimization_measure_serial_id, 474 kyp->provider_name, 475 NULL, /* provider_account_id */ 476 NULL, /* provider_legitimziation_id */ 477 &kyp->process_row); 478 if (qs < 0) 479 { 480 GNUNET_break (0); 481 return TALER_MHD_reply_with_error ( 482 rc->connection, 483 MHD_HTTP_INTERNAL_SERVER_ERROR, 484 TALER_EC_GENERIC_DB_STORE_FAILED, 485 "insert_kyc_requirement_process"); 486 } 487 488 kyp->ih = kyp->ih_logic->initiate ( 489 kyp->ih_logic->cls, 490 pd, 491 &kyp->h_payto, 492 kyp->process_row, 493 context, 494 &initiate_cb, 495 kyp); 496 if (NULL == kyp->ih) 497 { 498 GNUNET_break (0); 499 return TALER_MHD_reply_with_error ( 500 rc->connection, 501 MHD_HTTP_INTERNAL_SERVER_ERROR, 502 TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG, 503 "initiate KYC process"); 504 } 505 kyp->suspended = true; 506 GNUNET_CONTAINER_DLL_insert (kyp_head, 507 kyp_tail, 508 kyp); 509 MHD_suspend_connection (kyp->connection); 510 return MHD_YES; 511 } 512 513 if ( (TALER_EC_NONE != kyp->ec) || 514 (NULL == kyp->redirect_url) ) 515 { 516 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 517 "KYC process failed to start with error %d (%s)\n", 518 (int) kyp->ec, 519 kyp->hint); 520 if (TALER_EC_NONE == kyp->ec) 521 { 522 GNUNET_break (0); 523 kyp->ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 524 } 525 return TALER_MHD_reply_with_ec (rc->connection, 526 kyp->ec, 527 kyp->hint); 528 } 529 return TALER_MHD_REPLY_JSON_PACK ( 530 rc->connection, 531 MHD_HTTP_OK, 532 GNUNET_JSON_pack_string ("redirect_url", 533 kyp->redirect_url)); 534 } 535 536 537 /* end of taler-exchange-httpd_kyc-start.c */