taler-exchange-httpd_kyc-check.c (14219B)
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\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\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 break; 381 case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER: 382 if (! access_ok) 383 do_suspend = true; 384 break; 385 case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE: 386 if (! aml_review) 387 do_suspend = true; 388 break; 389 case TALER_EXCHANGE_KLPT_KYC_OK: 390 if (kyc_required) 391 do_suspend = true; 392 break; 393 } 394 } 395 } 396 397 if (do_suspend && 398 (access_ok || 399 (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) ) 400 { 401 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 402 "Suspending HTTP request on timeout (%s) for %d\n", 403 GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining ( 404 kyp->timeout), 405 true), 406 (int) kyp->lpt); 407 GNUNET_assert (NULL != kyp->eh); 408 kyp->suspended = true; 409 GNUNET_CONTAINER_DLL_insert (kyp_head, 410 kyp_tail, 411 kyp); 412 MHD_suspend_connection (kyp->connection); 413 return MHD_YES; 414 } 415 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 416 { 417 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 418 "Returning account unknown\n"); 419 return TALER_MHD_reply_with_error ( 420 rc->connection, 421 MHD_HTTP_NOT_FOUND, 422 TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN, 423 NULL); 424 } 425 } 426 427 if (! access_ok) 428 { 429 json_decref (jrules); 430 jrules = NULL; 431 if (GNUNET_is_zero (&kyp->account_pub)) 432 { 433 GNUNET_break_op (0); 434 return TALER_MHD_reply_with_error ( 435 rc->connection, 436 MHD_HTTP_CONFLICT, 437 TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN, 438 NULL); 439 } 440 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 441 "Returning authorization failed\n"); 442 return TALER_MHD_REPLY_JSON_PACK ( 443 rc->connection, 444 MHD_HTTP_FORBIDDEN, 445 TALER_JSON_pack_ec ( 446 TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED), 447 GNUNET_JSON_pack_data_auto ("expected_account_pub", 448 &kyp->account_pub)); 449 } 450 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 451 "KYC rules apply to %s:\n", 452 (GNUNET_SYSERR == is_wallet) 453 ? "unknown account type" 454 : ( (GNUNET_YES == is_wallet) 455 ? "wallet" 456 : "account")); 457 if (NULL != jrules) 458 json_dumpf (jrules, 459 stderr, 460 JSON_INDENT (2)); 461 462 jlimits = TALER_KYCLOGIC_rules_to_limits (jrules, 463 is_wallet); 464 if (NULL == jlimits) 465 { 466 GNUNET_break_op (0); 467 json_decref (jrules); 468 jrules = NULL; 469 return TALER_MHD_reply_with_error ( 470 rc->connection, 471 MHD_HTTP_INTERNAL_SERVER_ERROR, 472 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 473 "/kyc-check: rules_to_limits failed"); 474 } 475 json_decref (jrules); 476 jrules = NULL; 477 478 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 479 "KYC limits apply:\n"); 480 json_dumpf (jlimits, 481 stderr, 482 JSON_INDENT (2)); 483 484 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 485 "Returning KYC %s\n", 486 kyc_required ? "required" : "optional"); 487 return TALER_MHD_REPLY_JSON_PACK ( 488 rc->connection, 489 kyc_required 490 ? MHD_HTTP_ACCEPTED 491 : MHD_HTTP_OK, 492 GNUNET_JSON_pack_bool ("aml_review", 493 aml_review), 494 GNUNET_JSON_pack_uint64 ("rule_gen", 495 rule_gen), 496 GNUNET_JSON_pack_data_auto ("access_token", 497 &access_token), 498 GNUNET_JSON_pack_allow_null ( 499 GNUNET_JSON_pack_array_steal ("limits", 500 jlimits))); 501 } 502 503 504 /* end of taler-exchange-httpd_kyc-check.c */