taler-exchange-httpd_kyc-check.c (14346B)
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-check.c 18 * @brief Handle request for generic 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 <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-check.h" 34 #include "taler-exchange-httpd_kyc-wallet.h" 35 #include "taler-exchange-httpd_responses.h" 36 37 /** 38 * Reserve GET request that is long-polling. 39 */ 40 struct KycPoller 41 { 42 /** 43 * Kept in a DLL. 44 */ 45 struct KycPoller *next; 46 47 /** 48 * Kept in a DLL. 49 */ 50 struct KycPoller *prev; 51 52 /** 53 * Connection we are handling. 54 */ 55 struct MHD_Connection *connection; 56 57 /** 58 * Subscription for the database event we are 59 * waiting for. 60 */ 61 struct GNUNET_DB_EventHandler *eh; 62 63 /** 64 * Account for which we perform the KYC check. 65 */ 66 struct TALER_NormalizedPaytoHashP h_payto; 67 68 /** 69 * When will this request time out? 70 */ 71 struct GNUNET_TIME_Absolute timeout; 72 73 /** 74 * Signature by the account owner authorizing this 75 * operation. 76 */ 77 union TALER_AccountSignatureP account_sig; 78 79 /** 80 * Public key from the account owner authorizing this 81 * operation. Optional, see @e have_pub. 82 */ 83 union TALER_AccountPublicKeyP account_pub; 84 85 /** 86 * Generation of KYC rules already known to the client 87 * (when long-polling). Do not send these rules again. 88 */ 89 uint64_t min_rule; 90 91 /** 92 * What are we long-polling for (if anything)? 93 */ 94 enum TALER_EXCHANGE_KycLongPollTarget lpt; 95 96 /** 97 * True if we are still suspended. 98 */ 99 bool suspended; 100 101 /** 102 * True if we have an @e account_pub. 103 */ 104 bool have_pub; 105 106 }; 107 108 109 /** 110 * Head of list of requests in long polling. 111 */ 112 static struct KycPoller *kyp_head; 113 114 /** 115 * Tail of list of requests in long polling. 116 */ 117 static struct KycPoller *kyp_tail; 118 119 120 void 121 TEH_kyc_check_cleanup () 122 { 123 struct KycPoller *kyp; 124 125 while (NULL != (kyp = kyp_head)) 126 { 127 GNUNET_CONTAINER_DLL_remove (kyp_head, 128 kyp_tail, 129 kyp); 130 if (kyp->suspended) 131 { 132 kyp->suspended = false; 133 MHD_resume_connection (kyp->connection); 134 } 135 } 136 } 137 138 139 /** 140 * Function called once a connection is done to 141 * clean up the `struct ReservePoller` state. 142 * 143 * @param rc context to clean up for 144 */ 145 static void 146 kyp_cleanup (struct TEH_RequestContext *rc) 147 { 148 struct KycPoller *kyp = rc->rh_ctx; 149 150 GNUNET_assert (! kyp->suspended); 151 if (NULL != kyp->eh) 152 { 153 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 154 "Cancelling DB event listening (KYP cleanup)\n"); 155 TEH_plugin->event_listen_cancel (TEH_plugin->cls, 156 kyp->eh); 157 kyp->eh = NULL; 158 } 159 GNUNET_free (kyp); 160 } 161 162 163 /** 164 * Function called on events received from Postgres. 165 * Wakes up long pollers. 166 * 167 * @param cls the `struct TEH_RequestContext *` 168 * @param extra additional event data provided 169 * @param extra_size number of bytes in @a extra 170 */ 171 static void 172 db_event_cb (void *cls, 173 const void *extra, 174 size_t extra_size) 175 { 176 struct TEH_RequestContext *rc = cls; 177 struct KycPoller *kyp = rc->rh_ctx; 178 struct GNUNET_AsyncScopeSave old_scope; 179 180 (void) extra; 181 (void) extra_size; 182 if (! kyp->suspended) 183 return; /* event triggered while main transaction 184 was still running, or got multiple wake-up events */ 185 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 186 "Received KYC update event\n"); 187 kyp->suspended = false; 188 GNUNET_async_scope_enter (&rc->async_scope_id, 189 &old_scope); 190 TEH_check_invariants (); 191 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 192 "Resuming from long-polling on KYC status\n"); 193 GNUNET_CONTAINER_DLL_remove (kyp_head, 194 kyp_tail, 195 kyp); 196 MHD_resume_connection (kyp->connection); 197 TALER_MHD_daemon_trigger (); 198 TEH_check_invariants (); 199 GNUNET_async_scope_restore (&old_scope); 200 } 201 202 203 MHD_RESULT 204 TEH_handler_kyc_check ( 205 struct TEH_RequestContext *rc, 206 const char *const args[1]) 207 { 208 struct KycPoller *kyp = rc->rh_ctx; 209 json_t *jrules = NULL; 210 json_t *jlimits = NULL; 211 union TALER_AccountPublicKeyP reserve_pub; 212 struct TALER_AccountAccessTokenP access_token; 213 bool aml_review; 214 bool kyc_required; 215 bool access_ok = false; 216 enum GNUNET_GenericReturnValue is_wallet; 217 uint64_t rule_gen = 0; 218 219 if (NULL == kyp) 220 { 221 bool sig_required = true; 222 223 kyp = GNUNET_new (struct KycPoller); 224 kyp->connection = rc->connection; 225 rc->rh_ctx = kyp; 226 rc->rh_cleaner = &kyp_cleanup; 227 228 if (GNUNET_OK != 229 GNUNET_STRINGS_string_to_data (args[0], 230 strlen (args[0]), 231 &kyp->h_payto, 232 sizeof (kyp->h_payto))) 233 { 234 GNUNET_break_op (0); 235 return TALER_MHD_reply_with_error ( 236 rc->connection, 237 MHD_HTTP_BAD_REQUEST, 238 TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED, 239 "h_payto"); 240 } 241 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 242 "Checking KYC status for normalized payto hash %s\n", 243 args[0]); 244 TALER_MHD_parse_request_header_auto ( 245 rc->connection, 246 TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE, 247 &kyp->account_sig, 248 sig_required); 249 TALER_MHD_parse_request_header_auto ( 250 rc->connection, 251 TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY, 252 &kyp->account_pub, 253 kyp->have_pub); 254 TALER_MHD_parse_request_timeout (rc->connection, 255 &kyp->timeout); 256 { 257 uint64_t num = 0; 258 int val; 259 260 TALER_MHD_parse_request_number (rc->connection, 261 "lpt", 262 &num); 263 val = (int) num; 264 if ( (val < 0) || 265 (val > TALER_EXCHANGE_KLPT_MAX) ) 266 { 267 /* Protocol violation, but we can be graceful and 268 just ignore the long polling! */ 269 GNUNET_break_op (0); 270 val = TALER_EXCHANGE_KLPT_NONE; 271 } 272 kyp->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val; 273 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 274 "Long polling for target %d with timeout %s\n", 275 val, 276 GNUNET_TIME_relative2s ( 277 GNUNET_TIME_absolute_get_remaining ( 278 kyp->timeout), 279 true)); 280 } 281 TALER_MHD_parse_request_number (rc->connection, 282 "min_rule", 283 &kyp->min_rule); 284 /* long polling needed? */ 285 if (GNUNET_TIME_absolute_is_future (kyp->timeout)) 286 { 287 struct TALER_KycCompletedEventP rep = { 288 .header.size = htons (sizeof (rep)), 289 .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), 290 .h_payto = kyp->h_payto 291 }; 292 293 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 294 "Starting DB event listening for KYC COMPLETED\n"); 295 kyp->eh = TEH_plugin->event_listen ( 296 TEH_plugin->cls, 297 GNUNET_TIME_absolute_get_remaining (kyp->timeout), 298 &rep.header, 299 &db_event_cb, 300 rc); 301 } 302 } /* end initialization */ 303 304 if (! TEH_enable_kyc) 305 { 306 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 307 "KYC not enabled\n"); 308 return TALER_MHD_reply_static ( 309 rc->connection, 310 MHD_HTTP_NO_CONTENT, 311 NULL, 312 NULL, 313 0); 314 } 315 316 { 317 enum GNUNET_DB_QueryStatus qs; 318 bool do_suspend; 319 320 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 321 "Looking up KYC requirements for account %s (%s)\n", 322 TALER_B2S (&kyp->h_payto), 323 kyp->have_pub ? "with account-pub" : "legacy wallet"); 324 qs = TEH_plugin->lookup_kyc_requirement_by_row ( 325 TEH_plugin->cls, 326 &kyp->h_payto, 327 kyp->have_pub, 328 &kyp->account_pub, 329 &is_wallet, 330 &reserve_pub.reserve_pub, 331 &access_token, 332 &rule_gen, 333 &jrules, 334 &aml_review, 335 &kyc_required); 336 if (qs < 0) 337 { 338 GNUNET_break (0); 339 return TALER_MHD_reply_with_ec ( 340 rc->connection, 341 TALER_EC_GENERIC_DB_STORE_FAILED, 342 "lookup_kyc_requirement_by_row"); 343 } 344 345 do_suspend = false; 346 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 347 { 348 /* account unknown */ 349 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 350 "Account unknown!\n"); 351 if ( (TALER_EXCHANGE_KLPT_NONE != kyp->lpt) && 352 (TALER_EXCHANGE_KLPT_KYC_OK != kyp->lpt) && 353 (GNUNET_TIME_absolute_is_future (kyp->timeout)) ) 354 { 355 do_suspend = true; 356 access_ok = true; /* for now */ 357 } 358 } 359 else 360 { 361 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 362 "Found rule %llu (client wants > %llu)\n", 363 (unsigned long long) rule_gen, 364 (unsigned long long) kyp->min_rule); 365 access_ok = 366 ( (! GNUNET_is_zero (&kyp->account_pub) && 367 (GNUNET_OK == 368 TALER_account_kyc_auth_verify (&kyp->account_pub, 369 &kyp->account_sig)) ) || 370 (! GNUNET_is_zero (&reserve_pub) && 371 (GNUNET_OK == 372 TALER_account_kyc_auth_verify (&reserve_pub, 373 &kyp->account_sig)) ) ); 374 if (GNUNET_TIME_absolute_is_future (kyp->timeout) && 375 (rule_gen <= kyp->min_rule) ) 376 { 377 switch (kyp->lpt) 378 { 379 case TALER_EXCHANGE_KLPT_NONE: 380 /* If KLPT is not given, just go for rule generation */ 381 do_suspend = true; 382 break; 383 case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER: 384 if (! access_ok) 385 do_suspend = true; 386 break; 387 case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE: 388 if (! aml_review) 389 do_suspend = true; 390 break; 391 case TALER_EXCHANGE_KLPT_KYC_OK: 392 if (kyc_required) 393 do_suspend = true; 394 break; 395 } 396 } 397 } 398 399 if (do_suspend && 400 (access_ok || 401 (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) ) 402 { 403 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 404 "Suspending HTTP request on timeout (%s) for %d\n", 405 GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining ( 406 kyp->timeout), 407 true), 408 (int) kyp->lpt); 409 GNUNET_assert (NULL != kyp->eh); 410 kyp->suspended = true; 411 GNUNET_CONTAINER_DLL_insert (kyp_head, 412 kyp_tail, 413 kyp); 414 MHD_suspend_connection (kyp->connection); 415 return MHD_YES; 416 } 417 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 418 { 419 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 420 "Returning account unknown\n"); 421 return TALER_MHD_reply_with_error ( 422 rc->connection, 423 MHD_HTTP_NOT_FOUND, 424 TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN, 425 NULL); 426 } 427 } 428 429 if (! access_ok) 430 { 431 json_decref (jrules); 432 jrules = NULL; 433 if (GNUNET_is_zero (&kyp->account_pub)) 434 { 435 GNUNET_break_op (0); 436 return TALER_MHD_reply_with_error ( 437 rc->connection, 438 MHD_HTTP_CONFLICT, 439 TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN, 440 NULL); 441 } 442 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 443 "Returning authorization failed\n"); 444 return TALER_MHD_REPLY_JSON_PACK ( 445 rc->connection, 446 MHD_HTTP_FORBIDDEN, 447 TALER_JSON_pack_ec ( 448 TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED), 449 GNUNET_JSON_pack_data_auto ("expected_account_pub", 450 &kyp->account_pub)); 451 } 452 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 453 "KYC rules apply to %s:\n", 454 (GNUNET_SYSERR == is_wallet) 455 ? "unknown account type" 456 : ( (GNUNET_YES == is_wallet) 457 ? "wallet" 458 : "account")); 459 if (NULL != jrules) 460 json_dumpf (jrules, 461 stderr, 462 JSON_INDENT (2)); 463 464 jlimits = TALER_KYCLOGIC_rules_to_limits (jrules, 465 is_wallet); 466 if (NULL == jlimits) 467 { 468 GNUNET_break_op (0); 469 json_decref (jrules); 470 jrules = NULL; 471 return TALER_MHD_reply_with_error ( 472 rc->connection, 473 MHD_HTTP_INTERNAL_SERVER_ERROR, 474 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 475 "/kyc-check: rules_to_limits failed"); 476 } 477 json_decref (jrules); 478 jrules = NULL; 479 480 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 481 "KYC limits apply:\n"); 482 json_dumpf (jlimits, 483 stderr, 484 JSON_INDENT (2)); 485 486 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 487 "Returning KYC %s\n", 488 kyc_required ? "required" : "optional"); 489 return TALER_MHD_REPLY_JSON_PACK ( 490 rc->connection, 491 kyc_required 492 ? MHD_HTTP_ACCEPTED 493 : MHD_HTTP_OK, 494 GNUNET_JSON_pack_bool ("aml_review", 495 aml_review), 496 GNUNET_JSON_pack_uint64 ("rule_gen", 497 rule_gen), 498 GNUNET_JSON_pack_data_auto ("access_token", 499 &access_token), 500 GNUNET_JSON_pack_allow_null ( 501 GNUNET_JSON_pack_array_steal ("limits", 502 jlimits))); 503 } 504 505 506 /* end of taler-exchange-httpd_kyc-check.c */