plugin_kyclogic_persona.c (71220B)
1 /* 2 This file is part of GNU Taler 3 Copyright (C) 2022, 2023 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.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file plugin_kyclogic_persona.c 18 * @brief persona for an authentication flow logic 19 * @author Christian Grothoff 20 */ 21 #include "taler/taler_kyclogic_plugin.h" 22 #include "taler/taler_mhd_lib.h" 23 #include "taler/taler_curl_lib.h" 24 #include "taler/taler_json_lib.h" 25 #include "taler/taler_kyclogic_lib.h" 26 #include "taler/taler_templating_lib.h" 27 #include <regex.h> 28 #include "taler/taler_util.h" 29 30 #define DEBUG 0 31 32 33 /** 34 * Which version of the persona API are we implementing? 35 */ 36 #define PERSONA_VERSION "2021-07-05" 37 38 /** 39 * Saves the state of a plugin. 40 */ 41 struct PluginState 42 { 43 44 /** 45 * Our base URL. 46 */ 47 char *exchange_base_url; 48 49 /** 50 * Our global configuration. 51 */ 52 const struct GNUNET_CONFIGURATION_Handle *cfg; 53 54 /** 55 * Context for CURL operations (useful to the event loop) 56 */ 57 struct GNUNET_CURL_Context *curl_ctx; 58 59 /** 60 * Context for integrating @e curl_ctx with the 61 * GNUnet event loop. 62 */ 63 struct GNUNET_CURL_RescheduleContext *curl_rc; 64 65 /** 66 * Authorization token to use when receiving webhooks from the Persona 67 * service. Optional. Note that webhooks are *global* and not per 68 * template. 69 */ 70 char *webhook_token; 71 72 73 }; 74 75 76 /** 77 * Keeps the plugin-specific state for 78 * a given configuration section. 79 */ 80 struct TALER_KYCLOGIC_ProviderDetails 81 { 82 83 /** 84 * Overall plugin state. 85 */ 86 struct PluginState *ps; 87 88 /** 89 * Configuration section that configured us. 90 */ 91 char *section; 92 93 /** 94 * Name of the provider, that is @e section without 95 * the "kyc-provider-" prefix. This is the name used 96 * in the database. 97 */ 98 char *provider_name; 99 100 /** 101 * Salt to use for idempotency. 102 */ 103 char *salt; 104 105 /** 106 * Authorization token to use when talking 107 * to the service. 108 */ 109 char *auth_token; 110 111 /** 112 * Template ID for the KYC check to perform. 113 */ 114 char *template_id; 115 116 /** 117 * Subdomain to use. 118 */ 119 char *subdomain; 120 121 /** 122 * Name of the program we use to convert outputs 123 * from Persona into our JSON inputs. 124 */ 125 char *conversion_binary; 126 127 /** 128 * Where to redirect the client upon completion. 129 */ 130 char *post_kyc_redirect_url; 131 132 /** 133 * Validity time for a successful KYC process. 134 */ 135 struct GNUNET_TIME_Relative validity; 136 137 /** 138 * Curl-ready authentication header to use. 139 */ 140 struct curl_slist *slist; 141 142 }; 143 144 145 /** 146 * Handle for an initiation operation. 147 */ 148 struct TALER_KYCLOGIC_InitiateHandle 149 { 150 151 /** 152 * Hash of the payto:// URI we are initiating the KYC for. 153 */ 154 struct TALER_NormalizedPaytoHashP h_payto; 155 156 /** 157 * UUID being checked. 158 */ 159 uint64_t legitimization_uuid; 160 161 /** 162 * Our configuration details. 163 */ 164 const struct TALER_KYCLOGIC_ProviderDetails *pd; 165 166 /** 167 * Continuation to call. 168 */ 169 TALER_KYCLOGIC_InitiateCallback cb; 170 171 /** 172 * Closure for @a cb. 173 */ 174 void *cb_cls; 175 176 /** 177 * Context for #TEH_curl_easy_post(). Keeps the data that must 178 * persist for Curl to make the upload. 179 */ 180 struct TALER_CURL_PostContext ctx; 181 182 /** 183 * Handle for the request. 184 */ 185 struct GNUNET_CURL_Job *job; 186 187 /** 188 * URL of the cURL request. 189 */ 190 char *url; 191 192 /** 193 * Request-specific headers to use. 194 */ 195 struct curl_slist *slist; 196 197 }; 198 199 200 /** 201 * Handle for an KYC proof operation. 202 */ 203 struct TALER_KYCLOGIC_ProofHandle 204 { 205 206 /** 207 * Overall plugin state. 208 */ 209 struct PluginState *ps; 210 211 /** 212 * Our configuration details. 213 */ 214 const struct TALER_KYCLOGIC_ProviderDetails *pd; 215 216 /** 217 * Continuation to call. 218 */ 219 TALER_KYCLOGIC_ProofCallback cb; 220 221 /** 222 * Closure for @e cb. 223 */ 224 void *cb_cls; 225 226 /** 227 * Connection we are handling. 228 */ 229 struct MHD_Connection *connection; 230 231 /** 232 * Task for asynchronous execution. 233 */ 234 struct GNUNET_SCHEDULER_Task *task; 235 236 /** 237 * Handle for the request. 238 */ 239 struct GNUNET_CURL_Job *job; 240 241 /** 242 * URL of the cURL request. 243 */ 244 char *url; 245 246 /** 247 * Handle to an external process that converts the 248 * Persona response to our internal format. 249 */ 250 struct TALER_JSON_ExternalConversion *ec; 251 252 /** 253 * Hash of the payto:// URI we are checking the KYC for. 254 */ 255 struct TALER_NormalizedPaytoHashP h_payto; 256 257 /** 258 * Row in the legitimization processes of the 259 * legitimization proof that is being checked. 260 */ 261 uint64_t process_row; 262 263 /** 264 * Account ID at the provider. 265 */ 266 char *provider_user_id; 267 268 /** 269 * Account ID from the service. 270 */ 271 char *account_id; 272 273 /** 274 * Inquiry ID at the provider. 275 */ 276 char *inquiry_id; 277 }; 278 279 280 /** 281 * Handle for an KYC Web hook operation. 282 */ 283 struct TALER_KYCLOGIC_WebhookHandle 284 { 285 286 /** 287 * Continuation to call when done. 288 */ 289 TALER_KYCLOGIC_WebhookCallback cb; 290 291 /** 292 * Closure for @a cb. 293 */ 294 void *cb_cls; 295 296 /** 297 * Task for asynchronous execution. 298 */ 299 struct GNUNET_SCHEDULER_Task *task; 300 301 /** 302 * Overall plugin state. 303 */ 304 struct PluginState *ps; 305 306 /** 307 * Our configuration details. 308 */ 309 const struct TALER_KYCLOGIC_ProviderDetails *pd; 310 311 /** 312 * Connection we are handling. 313 */ 314 struct MHD_Connection *connection; 315 316 /** 317 * Verification ID from the service. 318 */ 319 char *inquiry_id; 320 321 /** 322 * Account ID from the service. 323 */ 324 char *account_id; 325 326 /** 327 * URL of the cURL request. 328 */ 329 char *url; 330 331 /** 332 * Handle for the request. 333 */ 334 struct GNUNET_CURL_Job *job; 335 336 /** 337 * Response to return asynchronously. 338 */ 339 struct MHD_Response *resp; 340 341 /** 342 * ID of the template the webhook is about, 343 * according to the service. 344 */ 345 const char *template_id; 346 347 /** 348 * Handle to an external process that converts the 349 * Persona response to our internal format. 350 */ 351 struct TALER_JSON_ExternalConversion *ec; 352 353 /** 354 * Our account ID. 355 */ 356 struct TALER_NormalizedPaytoHashP h_payto; 357 358 /** 359 * UUID being checked. 360 */ 361 uint64_t process_row; 362 363 /** 364 * HTTP status returned by Persona to us. 365 */ 366 unsigned int persona_http_status; 367 368 /** 369 * HTTP response code to return asynchronously. 370 */ 371 unsigned int response_code; 372 373 /** 374 * True if @e h_payto is for a wallet. 375 */ 376 bool is_wallet; 377 }; 378 379 380 /** 381 * Release configuration resources previously loaded 382 * 383 * @param[in] pd configuration to release 384 */ 385 static void 386 persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) 387 { 388 curl_slist_free_all (pd->slist); 389 GNUNET_free (pd->auth_token); 390 GNUNET_free (pd->template_id); 391 GNUNET_free (pd->subdomain); 392 GNUNET_free (pd->conversion_binary); 393 GNUNET_free (pd->salt); 394 GNUNET_free (pd->section); 395 GNUNET_free (pd->provider_name); 396 GNUNET_free (pd->post_kyc_redirect_url); 397 GNUNET_free (pd); 398 } 399 400 401 /** 402 * Load the configuration of the KYC provider. 403 * 404 * @param cls closure 405 * @param provider_section_name configuration section to parse 406 * @return NULL if configuration is invalid 407 */ 408 static struct TALER_KYCLOGIC_ProviderDetails * 409 persona_load_configuration (void *cls, 410 const char *provider_section_name) 411 { 412 struct PluginState *ps = cls; 413 struct TALER_KYCLOGIC_ProviderDetails *pd; 414 415 pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails); 416 pd->ps = ps; 417 pd->section = GNUNET_strdup (provider_section_name); 418 pd->provider_name 419 = GNUNET_strdup ( 420 (0 == strncasecmp (provider_section_name, 421 "kyc-provider-", 422 strlen ("kyc-provider-"))) 423 ? &provider_section_name[strlen ("kyc-provider-")] 424 : provider_section_name); 425 if (GNUNET_OK != 426 GNUNET_CONFIGURATION_get_value_time (ps->cfg, 427 provider_section_name, 428 "KYC_PERSONA_VALIDITY", 429 &pd->validity)) 430 { 431 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 432 provider_section_name, 433 "KYC_PERSONA_VALIDITY"); 434 persona_unload_configuration (pd); 435 return NULL; 436 } 437 if (GNUNET_OK != 438 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 439 provider_section_name, 440 "KYC_PERSONA_AUTH_TOKEN", 441 &pd->auth_token)) 442 { 443 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 444 provider_section_name, 445 "KYC_PERSONA_AUTH_TOKEN"); 446 persona_unload_configuration (pd); 447 return NULL; 448 } 449 if (GNUNET_OK != 450 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 451 provider_section_name, 452 "KYC_PERSONA_SALT", 453 &pd->salt)) 454 { 455 uint32_t salt[8]; 456 457 GNUNET_CRYPTO_random_block (salt, 458 sizeof (salt)); 459 pd->salt = GNUNET_STRINGS_data_to_string_alloc (salt, 460 sizeof (salt)); 461 } 462 if (GNUNET_OK != 463 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 464 provider_section_name, 465 "KYC_PERSONA_SUBDOMAIN", 466 &pd->subdomain)) 467 { 468 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 469 provider_section_name, 470 "KYC_PERSONA_SUBDOMAIN"); 471 persona_unload_configuration (pd); 472 return NULL; 473 } 474 if (GNUNET_OK != 475 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 476 provider_section_name, 477 "KYC_PERSONA_CONVERTER_HELPER", 478 &pd->conversion_binary)) 479 { 480 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 481 provider_section_name, 482 "KYC_PERSONA_CONVERTER_HELPER"); 483 persona_unload_configuration (pd); 484 return NULL; 485 } 486 if (GNUNET_OK != 487 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 488 provider_section_name, 489 "KYC_PERSONA_POST_URL", 490 &pd->post_kyc_redirect_url)) 491 { 492 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 493 provider_section_name, 494 "KYC_PERSONA_POST_URL"); 495 persona_unload_configuration (pd); 496 return NULL; 497 } 498 if (GNUNET_OK != 499 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 500 provider_section_name, 501 "KYC_PERSONA_TEMPLATE_ID", 502 &pd->template_id)) 503 { 504 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 505 provider_section_name, 506 "KYC_PERSONA_TEMPLATE_ID"); 507 persona_unload_configuration (pd); 508 return NULL; 509 } 510 { 511 char *auth; 512 513 GNUNET_asprintf (&auth, 514 "%s: Bearer %s", 515 MHD_HTTP_HEADER_AUTHORIZATION, 516 pd->auth_token); 517 pd->slist = curl_slist_append (NULL, 518 auth); 519 GNUNET_free (auth); 520 GNUNET_asprintf (&auth, 521 "%s: %s", 522 MHD_HTTP_HEADER_ACCEPT, 523 "application/json"); 524 pd->slist = curl_slist_append (pd->slist, 525 auth); 526 GNUNET_free (auth); 527 pd->slist = curl_slist_append (pd->slist, 528 "Persona-Version: " 529 PERSONA_VERSION); 530 } 531 return pd; 532 } 533 534 535 /** 536 * Cancel KYC check initiation. 537 * 538 * @param[in] ih handle of operation to cancel 539 */ 540 static void 541 persona_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih) 542 { 543 if (NULL != ih->job) 544 { 545 GNUNET_CURL_job_cancel (ih->job); 546 ih->job = NULL; 547 } 548 GNUNET_free (ih->url); 549 TALER_curl_easy_post_finished (&ih->ctx); 550 curl_slist_free_all (ih->slist); 551 GNUNET_free (ih); 552 } 553 554 555 /** 556 * Function called when we're done processing the 557 * HTTP POST "/api/v1/inquiries" request. 558 * 559 * @param cls the `struct TALER_KYCLOGIC_InitiateHandle` 560 * @param response_code HTTP response code, 0 on error 561 * @param response parsed JSON result, NULL on error 562 */ 563 static void 564 handle_initiate_finished (void *cls, 565 long response_code, 566 const void *response) 567 { 568 struct TALER_KYCLOGIC_InitiateHandle *ih = cls; 569 const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; 570 const json_t *j = response; 571 char *url; 572 json_t *data; 573 const char *type; 574 const char *inquiry_id; 575 const char *persona_account_id; 576 const char *ename; 577 unsigned int eline; 578 struct GNUNET_JSON_Specification spec[] = { 579 GNUNET_JSON_spec_string ("type", 580 &type), 581 GNUNET_JSON_spec_string ("id", 582 &inquiry_id), 583 GNUNET_JSON_spec_end () 584 }; 585 586 ih->job = NULL; 587 switch (response_code) 588 { 589 case MHD_HTTP_CREATED: 590 /* handled below */ 591 break; 592 case MHD_HTTP_UNAUTHORIZED: 593 case MHD_HTTP_FORBIDDEN: 594 { 595 const char *msg; 596 597 msg = json_string_value ( 598 json_object_get ( 599 json_array_get ( 600 json_object_get (j, 601 "errors"), 602 0), 603 "title")); 604 605 ih->cb (ih->cb_cls, 606 TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED, 607 NULL, 608 NULL, 609 NULL, 610 msg); 611 persona_initiate_cancel (ih); 612 return; 613 } 614 case MHD_HTTP_NOT_FOUND: 615 case MHD_HTTP_CONFLICT: 616 { 617 const char *msg; 618 619 msg = json_string_value ( 620 json_object_get ( 621 json_array_get ( 622 json_object_get (j, 623 "errors"), 624 0), 625 "title")); 626 627 ih->cb (ih->cb_cls, 628 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY, 629 NULL, 630 NULL, 631 NULL, 632 msg); 633 persona_initiate_cancel (ih); 634 return; 635 } 636 case MHD_HTTP_BAD_REQUEST: 637 case MHD_HTTP_UNPROCESSABLE_CONTENT: 638 { 639 const char *msg; 640 641 GNUNET_break (0); 642 #if DEBUG 643 json_dumpf (j, 644 stderr, 645 JSON_INDENT (2)); 646 #endif 647 msg = json_string_value ( 648 json_object_get ( 649 json_array_get ( 650 json_object_get (j, 651 "errors"), 652 0), 653 "title")); 654 655 ih->cb (ih->cb_cls, 656 TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG, 657 NULL, 658 NULL, 659 NULL, 660 msg); 661 persona_initiate_cancel (ih); 662 return; 663 } 664 case MHD_HTTP_TOO_MANY_REQUESTS: 665 { 666 const char *msg; 667 668 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 669 "Rate limiting requested:\n"); 670 #if DEBUG 671 json_dumpf (j, 672 stderr, 673 JSON_INDENT (2)); 674 #endif 675 msg = json_string_value ( 676 json_object_get ( 677 json_array_get ( 678 json_object_get (j, 679 "errors"), 680 0), 681 "title")); 682 ih->cb (ih->cb_cls, 683 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED, 684 NULL, 685 NULL, 686 NULL, 687 msg); 688 persona_initiate_cancel (ih); 689 return; 690 } 691 default: 692 { 693 char *err; 694 695 GNUNET_break_op (0); 696 #if DEBUG 697 json_dumpf (j, 698 stderr, 699 JSON_INDENT (2)); 700 #endif 701 GNUNET_asprintf (&err, 702 "Unexpected HTTP status %u from Persona\n", 703 (unsigned int) response_code); 704 ih->cb (ih->cb_cls, 705 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY, 706 NULL, 707 NULL, 708 NULL, 709 err); 710 GNUNET_free (err); 711 persona_initiate_cancel (ih); 712 return; 713 } 714 } 715 data = json_object_get (j, 716 "data"); 717 if (NULL == data) 718 { 719 GNUNET_break_op (0); 720 #if DEBUG 721 json_dumpf (j, 722 stderr, 723 JSON_INDENT (2)); 724 #endif 725 ih->cb (ih->cb_cls, 726 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY, 727 NULL, 728 NULL, 729 NULL, 730 "'data' field missing in Persona response"); 731 persona_initiate_cancel (ih); 732 return; 733 } 734 735 if (GNUNET_OK != 736 GNUNET_JSON_parse (data, 737 spec, 738 &ename, 739 &eline)) 740 { 741 GNUNET_break_op (0); 742 #if DEBUG 743 json_dumpf (j, 744 stderr, 745 JSON_INDENT (2)); 746 #endif 747 ih->cb (ih->cb_cls, 748 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY, 749 NULL, 750 NULL, 751 NULL, 752 ename); 753 persona_initiate_cancel (ih); 754 return; 755 } 756 persona_account_id 757 = json_string_value ( 758 json_object_get ( 759 json_object_get ( 760 json_object_get ( 761 json_object_get (data, 762 "relationships"), 763 "account"), 764 "data"), 765 "id")); 766 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 767 "Starting inquiry %s for Persona account %s\n", 768 inquiry_id, 769 persona_account_id); 770 { 771 char *inquiry_id_encoded; 772 773 inquiry_id_encoded = TALER_urlencode (inquiry_id); 774 GNUNET_asprintf (&url, 775 "https://%s.withpersona.com/verify" 776 "?inquiry-id=%s", 777 pd->subdomain, 778 inquiry_id_encoded); 779 GNUNET_free (inquiry_id_encoded); 780 } 781 ih->cb (ih->cb_cls, 782 TALER_EC_NONE, 783 url, 784 persona_account_id, 785 inquiry_id, 786 NULL); 787 GNUNET_free (url); 788 persona_initiate_cancel (ih); 789 } 790 791 792 /** 793 * Initiate KYC check. 794 * 795 * @param cls the @e cls of this struct with the plugin-specific state 796 * @param pd provider configuration details 797 * @param account_id which account to trigger process for 798 * @param legitimization_uuid unique ID for the legitimization process 799 * @param context additional contextual information for the legi process 800 * @param cb function to call with the result 801 * @param cb_cls closure for @a cb 802 * @return handle to cancel operation early 803 */ 804 static struct TALER_KYCLOGIC_InitiateHandle * 805 persona_initiate (void *cls, 806 const struct TALER_KYCLOGIC_ProviderDetails *pd, 807 const struct TALER_NormalizedPaytoHashP *account_id, 808 uint64_t legitimization_uuid, 809 const json_t *context, 810 TALER_KYCLOGIC_InitiateCallback cb, 811 void *cb_cls) 812 { 813 struct PluginState *ps = cls; 814 struct TALER_KYCLOGIC_InitiateHandle *ih; 815 json_t *body; 816 CURL *eh; 817 818 (void) context; 819 eh = curl_easy_init (); 820 if (NULL == eh) 821 { 822 GNUNET_break (0); 823 return NULL; 824 } 825 ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle); 826 ih->legitimization_uuid = legitimization_uuid; 827 ih->cb = cb; 828 ih->cb_cls = cb_cls; 829 ih->h_payto = *account_id; 830 ih->pd = pd; 831 GNUNET_asprintf (&ih->url, 832 "https://withpersona.com/api/v1/inquiries"); 833 { 834 char *payto_s; 835 char *proof_url; 836 char ref_s[24]; 837 838 GNUNET_snprintf (ref_s, 839 sizeof (ref_s), 840 "%llu", 841 (unsigned long long) ih->legitimization_uuid); 842 payto_s = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto, 843 sizeof (ih->h_payto)); 844 GNUNET_break ('/' == 845 pd->ps->exchange_base_url[strlen ( 846 pd->ps->exchange_base_url) - 1]); 847 GNUNET_asprintf (&proof_url, 848 "%skyc-proof/%s?state=%s", 849 pd->ps->exchange_base_url, 850 &pd->section[strlen ("kyc-provider-")], 851 payto_s); 852 body = GNUNET_JSON_PACK ( 853 GNUNET_JSON_pack_object_steal ( 854 "data", 855 GNUNET_JSON_PACK ( 856 GNUNET_JSON_pack_object_steal ( 857 "attributes", 858 GNUNET_JSON_PACK ( 859 GNUNET_JSON_pack_string ("inquiry_template_id", 860 pd->template_id), 861 GNUNET_JSON_pack_string ("reference_id", 862 ref_s), 863 GNUNET_JSON_pack_string ("redirect_uri", 864 proof_url) 865 ))))); 866 GNUNET_assert (NULL != body); 867 GNUNET_free (payto_s); 868 GNUNET_free (proof_url); 869 } 870 GNUNET_break (CURLE_OK == 871 curl_easy_setopt (eh, 872 CURLOPT_VERBOSE, 873 0)); 874 GNUNET_assert (CURLE_OK == 875 curl_easy_setopt (eh, 876 CURLOPT_MAXREDIRS, 877 1L)); 878 GNUNET_break (CURLE_OK == 879 curl_easy_setopt (eh, 880 CURLOPT_URL, 881 ih->url)); 882 ih->ctx.disable_compression = true; 883 if (GNUNET_OK != 884 TALER_curl_easy_post (&ih->ctx, 885 eh, 886 body)) 887 { 888 GNUNET_break (0); 889 GNUNET_free (ih->url); 890 GNUNET_free (ih); 891 curl_easy_cleanup (eh); 892 json_decref (body); 893 return NULL; 894 } 895 json_decref (body); 896 ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx, 897 eh, 898 ih->ctx.headers, 899 &handle_initiate_finished, 900 ih); 901 GNUNET_CURL_extend_headers (ih->job, 902 pd->slist); 903 { 904 char *ikh; 905 906 GNUNET_asprintf (&ikh, 907 "Idempotency-Key: %llu-%s", 908 (unsigned long long) ih->legitimization_uuid, 909 pd->salt); 910 ih->slist = curl_slist_append (NULL, 911 ikh); 912 GNUNET_free (ikh); 913 } 914 GNUNET_CURL_extend_headers (ih->job, 915 ih->slist); 916 return ih; 917 } 918 919 920 /** 921 * Cancel KYC proof. 922 * 923 * @param[in] ph handle of operation to cancel 924 */ 925 static void 926 persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) 927 { 928 if (NULL != ph->job) 929 { 930 GNUNET_CURL_job_cancel (ph->job); 931 ph->job = NULL; 932 } 933 if (NULL != ph->ec) 934 { 935 TALER_JSON_external_conversion_stop (ph->ec); 936 ph->ec = NULL; 937 } 938 GNUNET_free (ph->url); 939 GNUNET_free (ph->provider_user_id); 940 GNUNET_free (ph->account_id); 941 GNUNET_free (ph->inquiry_id); 942 GNUNET_free (ph); 943 } 944 945 946 /** 947 * Call @a ph callback with the operation result. 948 * 949 * @param ph proof handle to generate reply for 950 * @param status status to return 951 * @param account_id account to return 952 * @param inquiry_id inquiry ID to supply, NULL if unknown 953 * @param http_status HTTP status to use 954 * @param template template to instantiate 955 * @param[in] body body for the template to use (reference 956 * is consumed) 957 */ 958 static void 959 proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph, 960 enum TALER_KYCLOGIC_KycStatus status, 961 const char *account_id, 962 const char *inquiry_id, 963 unsigned int http_status, 964 const char *template, 965 json_t *body) 966 { 967 struct MHD_Response *resp; 968 enum GNUNET_GenericReturnValue ret; 969 970 /* This API is not usable for successful replies */ 971 GNUNET_assert (TALER_KYCLOGIC_STATUS_SUCCESS != status); 972 ret = TALER_TEMPLATING_build (ph->connection, 973 &http_status, 974 template, 975 NULL, 976 NULL, 977 body, 978 &resp); 979 json_decref (body); 980 if (GNUNET_SYSERR == ret) 981 { 982 GNUNET_break (0); 983 resp = NULL; /* good luck */ 984 } 985 else 986 { 987 GNUNET_break (MHD_NO != 988 MHD_add_response_header (resp, 989 MHD_HTTP_HEADER_CONTENT_TYPE, 990 "text/html")); 991 } 992 ph->cb (ph->cb_cls, 993 status, 994 ph->pd->provider_name, 995 account_id, 996 inquiry_id, 997 GNUNET_TIME_UNIT_ZERO_ABS, 998 NULL, 999 http_status, 1000 resp); 1001 } 1002 1003 1004 /** 1005 * Call @a ph callback with HTTP error response. 1006 * 1007 * @param ph proof handle to generate reply for 1008 * @param inquiry_id inquiry ID to supply 1009 * @param http_status HTTP status to use 1010 * @param template template to instantiate 1011 * @param[in] body body for the template to use (reference 1012 * is consumed) 1013 */ 1014 static void 1015 proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph, 1016 const char *inquiry_id, 1017 unsigned int http_status, 1018 const char *template, 1019 json_t *body) 1020 { 1021 proof_generic_reply (ph, 1022 TALER_KYCLOGIC_STATUS_PROVIDER_FAILED, 1023 NULL, /* user id */ 1024 inquiry_id, 1025 http_status, 1026 template, 1027 body); 1028 } 1029 1030 1031 /** 1032 * Return a response for the @a ph request indicating a 1033 * protocol violation by the Persona server. 1034 * 1035 * @param[in,out] ph request we are processing 1036 * @param response_code HTTP status returned by Persona 1037 * @param inquiry_id ID of the inquiry this is about 1038 * @param detail where the response was wrong 1039 * @param data full response data to output 1040 */ 1041 static void 1042 return_invalid_response (struct TALER_KYCLOGIC_ProofHandle *ph, 1043 unsigned int response_code, 1044 const char *inquiry_id, 1045 const char *detail, 1046 const json_t *data) 1047 { 1048 proof_reply_error ( 1049 ph, 1050 inquiry_id, 1051 MHD_HTTP_BAD_GATEWAY, 1052 "persona-invalid-response", 1053 GNUNET_JSON_PACK ( 1054 GNUNET_JSON_pack_uint64 ("persona_http_status", 1055 response_code), 1056 GNUNET_JSON_pack_allow_null ( 1057 GNUNET_JSON_pack_string ("persona_inquiry_id", 1058 inquiry_id)), 1059 TALER_JSON_pack_ec ( 1060 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY), 1061 GNUNET_JSON_pack_string ("detail", 1062 detail), 1063 GNUNET_JSON_pack_allow_null ( 1064 GNUNET_JSON_pack_object_incref ("data", 1065 (json_t *) 1066 data)))); 1067 } 1068 1069 1070 /** 1071 * Start the external conversion helper. 1072 * 1073 * @param pd configuration details 1074 * @param attr attributes to give to the helper 1075 * @param cb function to call with the result 1076 * @param cb_cls closure for @a cb 1077 * @return handle for the helper 1078 */ 1079 static struct TALER_JSON_ExternalConversion * 1080 start_conversion (const struct TALER_KYCLOGIC_ProviderDetails *pd, 1081 const json_t *attr, 1082 TALER_JSON_JsonCallback cb, 1083 void *cb_cls) 1084 { 1085 const char *argv[] = { 1086 pd->conversion_binary, 1087 "-a", 1088 pd->auth_token, 1089 NULL, 1090 }; 1091 1092 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1093 "Calling converter `%s' with JSON\n", 1094 pd->conversion_binary); 1095 #if DEBUG 1096 json_dumpf (attr, 1097 stderr, 1098 JSON_INDENT (2)); 1099 #endif 1100 return TALER_JSON_external_conversion_start ( 1101 attr, 1102 cb, 1103 cb_cls, 1104 pd->conversion_binary, 1105 argv); 1106 } 1107 1108 1109 /** 1110 * Type of a callback that receives a JSON @a result. 1111 * 1112 * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *` 1113 * @param status_type how did the process die 1114 * @param code termination status code from the process 1115 * @param attr result some JSON result, NULL if we failed to get an JSON output 1116 */ 1117 static void 1118 proof_post_conversion_cb (void *cls, 1119 enum GNUNET_OS_ProcessStatusType status_type, 1120 unsigned long code, 1121 const json_t *attr) 1122 { 1123 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1124 struct MHD_Response *resp; 1125 struct GNUNET_TIME_Absolute expiration; 1126 1127 ph->ec = NULL; 1128 if ( (NULL == attr) || 1129 (GNUNET_OS_PROCESS_EXITED != status_type) || 1130 (0 != code) ) 1131 { 1132 GNUNET_break_op (0); 1133 return_invalid_response (ph, 1134 MHD_HTTP_OK, 1135 ph->inquiry_id, 1136 "converter", 1137 NULL); 1138 persona_proof_cancel (ph); 1139 return; 1140 } 1141 expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity); 1142 resp = MHD_create_response_from_buffer_static (0, 1143 ""); 1144 GNUNET_break (MHD_YES == 1145 MHD_add_response_header (resp, 1146 MHD_HTTP_HEADER_LOCATION, 1147 ph->pd->post_kyc_redirect_url)); 1148 TALER_MHD_add_global_headers (resp, 1149 false); 1150 ph->cb (ph->cb_cls, 1151 TALER_KYCLOGIC_STATUS_SUCCESS, 1152 ph->pd->provider_name, 1153 ph->account_id, 1154 ph->inquiry_id, 1155 expiration, 1156 attr, 1157 MHD_HTTP_SEE_OTHER, 1158 resp); 1159 persona_proof_cancel (ph); 1160 } 1161 1162 1163 /** 1164 * Function called when we're done processing the 1165 * HTTP "/api/v1/inquiries/{inquiry-id}" request. 1166 * 1167 * @param cls the `struct TALER_KYCLOGIC_InitiateHandle` 1168 * @param response_code HTTP response code, 0 on error 1169 * @param response parsed JSON result, NULL on error 1170 */ 1171 static void 1172 handle_proof_finished (void *cls, 1173 long response_code, 1174 const void *response) 1175 { 1176 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1177 const json_t *j = response; 1178 const json_t *data = json_object_get (j, 1179 "data"); 1180 1181 ph->job = NULL; 1182 switch (response_code) 1183 { 1184 case MHD_HTTP_OK: 1185 { 1186 const char *inquiry_id = NULL; 1187 const char *account_id; 1188 const char *type = NULL; 1189 const json_t *attributes; 1190 const json_t *relationships; 1191 struct GNUNET_JSON_Specification spec[] = { 1192 GNUNET_JSON_spec_string ("type", 1193 &type), 1194 GNUNET_JSON_spec_string ("id", 1195 &inquiry_id), 1196 GNUNET_JSON_spec_object_const ("attributes", 1197 &attributes), 1198 GNUNET_JSON_spec_object_const ("relationships", 1199 &relationships), 1200 GNUNET_JSON_spec_end () 1201 }; 1202 1203 if ( (NULL == data) || 1204 (GNUNET_OK != 1205 GNUNET_JSON_parse (data, 1206 spec, 1207 NULL, NULL)) || 1208 (0 != strcasecmp (type, 1209 "inquiry")) ) 1210 { 1211 GNUNET_break_op (0); 1212 return_invalid_response (ph, 1213 response_code, 1214 inquiry_id, 1215 "data", 1216 data); 1217 break; 1218 } 1219 1220 { 1221 const char *status; /* "completed", what else? */ 1222 const char *reference_id; /* or legitimization number */ 1223 const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */ 1224 struct GNUNET_JSON_Specification ispec[] = { 1225 GNUNET_JSON_spec_string ("status", 1226 &status), 1227 GNUNET_JSON_spec_string ("reference-id", 1228 &reference_id), 1229 GNUNET_JSON_spec_mark_optional ( 1230 GNUNET_JSON_spec_string ("expired-at", 1231 &expired_at), 1232 NULL), 1233 GNUNET_JSON_spec_end () 1234 }; 1235 1236 if (GNUNET_OK != 1237 GNUNET_JSON_parse (attributes, 1238 ispec, 1239 NULL, NULL)) 1240 { 1241 GNUNET_break_op (0); 1242 return_invalid_response (ph, 1243 response_code, 1244 inquiry_id, 1245 "data-attributes", 1246 data); 1247 break; 1248 } 1249 { 1250 unsigned long long idr; 1251 char dummy; 1252 1253 if ( (1 != sscanf (reference_id, 1254 "%llu%c", 1255 &idr, 1256 &dummy)) || 1257 (idr != ph->process_row) ) 1258 { 1259 GNUNET_break_op (0); 1260 return_invalid_response (ph, 1261 response_code, 1262 inquiry_id, 1263 "data-attributes-reference_id", 1264 data); 1265 break; 1266 } 1267 } 1268 1269 if (0 != strcmp (inquiry_id, 1270 ph->inquiry_id)) 1271 { 1272 GNUNET_break_op (0); 1273 return_invalid_response (ph, 1274 response_code, 1275 inquiry_id, 1276 "data-id", 1277 data); 1278 break; 1279 } 1280 1281 account_id = json_string_value ( 1282 json_object_get ( 1283 json_object_get ( 1284 json_object_get ( 1285 relationships, 1286 "account"), 1287 "data"), 1288 "id")); 1289 1290 if (0 != strcasecmp (status, 1291 "completed")) 1292 { 1293 proof_generic_reply ( 1294 ph, 1295 TALER_KYCLOGIC_STATUS_FAILED, 1296 account_id, 1297 inquiry_id, 1298 MHD_HTTP_OK, 1299 "persona-kyc-failed", 1300 GNUNET_JSON_PACK ( 1301 GNUNET_JSON_pack_uint64 ("persona_http_status", 1302 response_code), 1303 GNUNET_JSON_pack_string ("persona_inquiry_id", 1304 inquiry_id), 1305 GNUNET_JSON_pack_allow_null ( 1306 GNUNET_JSON_pack_object_incref ("data", 1307 (json_t *) 1308 data)))); 1309 break; 1310 } 1311 1312 if (NULL == account_id) 1313 { 1314 GNUNET_break_op (0); 1315 return_invalid_response (ph, 1316 response_code, 1317 inquiry_id, 1318 "data-relationships-account-data-id", 1319 data); 1320 break; 1321 } 1322 ph->account_id = GNUNET_strdup (account_id); 1323 ph->ec = start_conversion (ph->pd, 1324 j, 1325 &proof_post_conversion_cb, 1326 ph); 1327 if (NULL == ph->ec) 1328 { 1329 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1330 "Failed to start Persona conversion helper\n"); 1331 proof_reply_error ( 1332 ph, 1333 ph->inquiry_id, 1334 MHD_HTTP_BAD_GATEWAY, 1335 "persona-logic-failure", 1336 GNUNET_JSON_PACK ( 1337 TALER_JSON_pack_ec ( 1338 TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED))); 1339 break; 1340 } 1341 } 1342 return; /* continued in proof_post_conversion_cb */ 1343 } 1344 case MHD_HTTP_BAD_REQUEST: 1345 case MHD_HTTP_NOT_FOUND: 1346 case MHD_HTTP_CONFLICT: 1347 case MHD_HTTP_UNPROCESSABLE_CONTENT: 1348 /* These are errors with this code */ 1349 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1350 "PERSONA failed with response %u:\n", 1351 (unsigned int) response_code); 1352 #if DEBUG 1353 json_dumpf (j, 1354 stderr, 1355 JSON_INDENT (2)); 1356 #endif 1357 proof_reply_error ( 1358 ph, 1359 ph->inquiry_id, 1360 MHD_HTTP_BAD_GATEWAY, 1361 "persona-logic-failure", 1362 GNUNET_JSON_PACK ( 1363 GNUNET_JSON_pack_uint64 ("persona_http_status", 1364 response_code), 1365 TALER_JSON_pack_ec ( 1366 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY), 1367 1368 GNUNET_JSON_pack_allow_null ( 1369 GNUNET_JSON_pack_object_incref ("data", 1370 (json_t *) 1371 data)))); 1372 break; 1373 case MHD_HTTP_UNAUTHORIZED: 1374 /* These are failures of the exchange operator */ 1375 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1376 "Refused access with HTTP status code %u\n", 1377 (unsigned int) response_code); 1378 proof_reply_error ( 1379 ph, 1380 ph->inquiry_id, 1381 MHD_HTTP_BAD_GATEWAY, 1382 "persona-exchange-unauthorized", 1383 GNUNET_JSON_PACK ( 1384 GNUNET_JSON_pack_uint64 ("persona_http_status", 1385 response_code), 1386 TALER_JSON_pack_ec ( 1387 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED), 1388 GNUNET_JSON_pack_allow_null ( 1389 GNUNET_JSON_pack_object_incref ("data", 1390 (json_t *) 1391 data)))); 1392 break; 1393 case MHD_HTTP_PAYMENT_REQUIRED: 1394 /* These are failures of the exchange operator */ 1395 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1396 "Refused access with HTTP status code %u\n", 1397 (unsigned int) response_code); 1398 proof_reply_error ( 1399 ph, 1400 ph->inquiry_id, 1401 MHD_HTTP_SERVICE_UNAVAILABLE, 1402 "persona-exchange-unpaid", 1403 GNUNET_JSON_PACK ( 1404 GNUNET_JSON_pack_uint64 ("persona_http_status", 1405 response_code), 1406 TALER_JSON_pack_ec ( 1407 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED), 1408 GNUNET_JSON_pack_allow_null ( 1409 GNUNET_JSON_pack_object_incref ("data", 1410 (json_t *) 1411 data)))); 1412 break; 1413 case MHD_HTTP_REQUEST_TIMEOUT: 1414 /* These are networking issues */ 1415 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1416 "PERSONA failed with response %u:\n", 1417 (unsigned int) response_code); 1418 #if DEBUG 1419 json_dumpf (j, 1420 stderr, 1421 JSON_INDENT (2)); 1422 #endif 1423 proof_reply_error ( 1424 ph, 1425 ph->inquiry_id, 1426 MHD_HTTP_GATEWAY_TIMEOUT, 1427 "persona-network-timeout", 1428 GNUNET_JSON_PACK ( 1429 GNUNET_JSON_pack_uint64 ("persona_http_status", 1430 response_code), 1431 TALER_JSON_pack_ec ( 1432 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT), 1433 GNUNET_JSON_pack_allow_null ( 1434 GNUNET_JSON_pack_object_incref ("data", 1435 (json_t *) 1436 data)))); 1437 break; 1438 case MHD_HTTP_TOO_MANY_REQUESTS: 1439 /* This is a load issue */ 1440 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1441 "PERSONA failed with response %u:\n", 1442 (unsigned int) response_code); 1443 #if DEBUG 1444 json_dumpf (j, 1445 stderr, 1446 JSON_INDENT (2)); 1447 #endif 1448 proof_reply_error ( 1449 ph, 1450 ph->inquiry_id, 1451 MHD_HTTP_SERVICE_UNAVAILABLE, 1452 "persona-load-failure", 1453 GNUNET_JSON_PACK ( 1454 GNUNET_JSON_pack_uint64 ("persona_http_status", 1455 response_code), 1456 TALER_JSON_pack_ec ( 1457 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED), 1458 GNUNET_JSON_pack_allow_null ( 1459 GNUNET_JSON_pack_object_incref ("data", 1460 (json_t *) 1461 data)))); 1462 break; 1463 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1464 /* This is an issue with Persona */ 1465 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1466 "PERSONA failed with response %u:\n", 1467 (unsigned int) response_code); 1468 #if DEBUG 1469 json_dumpf (j, 1470 stderr, 1471 JSON_INDENT (2)); 1472 #endif 1473 proof_reply_error ( 1474 ph, 1475 ph->inquiry_id, 1476 MHD_HTTP_BAD_GATEWAY, 1477 "persona-provider-failure", 1478 GNUNET_JSON_PACK ( 1479 GNUNET_JSON_pack_uint64 ("persona_http_status", 1480 response_code), 1481 TALER_JSON_pack_ec ( 1482 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_ERROR), 1483 GNUNET_JSON_pack_allow_null ( 1484 GNUNET_JSON_pack_object_incref ("data", 1485 (json_t *) 1486 data)))); 1487 break; 1488 default: 1489 /* This is an issue with Persona */ 1490 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1491 "PERSONA failed with response %u:\n", 1492 (unsigned int) response_code); 1493 #if DEBUG 1494 json_dumpf (j, 1495 stderr, 1496 JSON_INDENT (2)); 1497 #endif 1498 proof_reply_error ( 1499 ph, 1500 ph->inquiry_id, 1501 MHD_HTTP_BAD_GATEWAY, 1502 "persona-invalid-response", 1503 GNUNET_JSON_PACK ( 1504 GNUNET_JSON_pack_uint64 ("persona_http_status", 1505 response_code), 1506 TALER_JSON_pack_ec ( 1507 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY), 1508 GNUNET_JSON_pack_allow_null ( 1509 GNUNET_JSON_pack_object_incref ("data", 1510 (json_t *) 1511 data)))); 1512 break; 1513 } 1514 persona_proof_cancel (ph); 1515 } 1516 1517 1518 /** 1519 * Check KYC status and return final result to human. 1520 * 1521 * @param cls the @e cls of this struct with the plugin-specific state 1522 * @param pd provider configuration details 1523 * @param connection MHD connection object (for HTTP headers) 1524 * @param account_id which account to trigger process for 1525 * @param process_row row in the legitimization processes table the legitimization is for 1526 * @param provider_user_id user ID (or NULL) the proof is for 1527 * @param inquiry_id legitimization ID the proof is for 1528 * @param cb function to call with the result 1529 * @param cb_cls closure for @a cb 1530 * @return handle to cancel operation early 1531 */ 1532 static struct TALER_KYCLOGIC_ProofHandle * 1533 persona_proof (void *cls, 1534 const struct TALER_KYCLOGIC_ProviderDetails *pd, 1535 struct MHD_Connection *connection, 1536 const struct TALER_NormalizedPaytoHashP *account_id, 1537 uint64_t process_row, 1538 const char *provider_user_id, 1539 const char *inquiry_id, 1540 TALER_KYCLOGIC_ProofCallback cb, 1541 void *cb_cls) 1542 { 1543 struct PluginState *ps = cls; 1544 struct TALER_KYCLOGIC_ProofHandle *ph; 1545 CURL *eh; 1546 1547 eh = curl_easy_init (); 1548 if (NULL == eh) 1549 { 1550 GNUNET_break (0); 1551 return NULL; 1552 } 1553 ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle); 1554 ph->ps = ps; 1555 ph->pd = pd; 1556 ph->cb = cb; 1557 ph->cb_cls = cb_cls; 1558 ph->connection = connection; 1559 ph->process_row = process_row; 1560 ph->h_payto = *account_id; 1561 /* Note: we do not expect this to be non-NULL */ 1562 if (NULL != provider_user_id) 1563 ph->provider_user_id = GNUNET_strdup (provider_user_id); 1564 if (NULL != inquiry_id) 1565 ph->inquiry_id = GNUNET_strdup (inquiry_id); 1566 { 1567 char *inquiry_id_encoded; 1568 1569 inquiry_id_encoded = TALER_urlencode (NULL != inquiry_id 1570 ? inquiry_id 1571 : ""); 1572 GNUNET_asprintf (&ph->url, 1573 "https://withpersona.com/api/v1/inquiries/%s", 1574 inquiry_id_encoded); 1575 GNUNET_free (inquiry_id_encoded); 1576 } 1577 GNUNET_break (CURLE_OK == 1578 curl_easy_setopt (eh, 1579 CURLOPT_VERBOSE, 1580 0)); 1581 GNUNET_assert (CURLE_OK == 1582 curl_easy_setopt (eh, 1583 CURLOPT_MAXREDIRS, 1584 1L)); 1585 GNUNET_break (CURLE_OK == 1586 curl_easy_setopt (eh, 1587 CURLOPT_URL, 1588 ph->url)); 1589 ph->job = GNUNET_CURL_job_add2 (ps->curl_ctx, 1590 eh, 1591 pd->slist, 1592 &handle_proof_finished, 1593 ph); 1594 return ph; 1595 } 1596 1597 1598 /** 1599 * Cancel KYC webhook execution. 1600 * 1601 * @param[in] wh handle of operation to cancel 1602 */ 1603 static void 1604 persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh) 1605 { 1606 if (NULL != wh->task) 1607 { 1608 GNUNET_SCHEDULER_cancel (wh->task); 1609 wh->task = NULL; 1610 } 1611 if (NULL != wh->job) 1612 { 1613 GNUNET_CURL_job_cancel (wh->job); 1614 wh->job = NULL; 1615 } 1616 if (NULL != wh->ec) 1617 { 1618 TALER_JSON_external_conversion_stop (wh->ec); 1619 wh->ec = NULL; 1620 } 1621 GNUNET_free (wh->account_id); 1622 GNUNET_free (wh->inquiry_id); 1623 GNUNET_free (wh->url); 1624 GNUNET_free (wh); 1625 } 1626 1627 1628 /** 1629 * Call @a wh callback with the operation result. 1630 * 1631 * @param wh proof handle to generate reply for 1632 * @param status status to return 1633 * @param account_id account to return 1634 * @param inquiry_id inquiry ID to supply, NULL if unknown 1635 * @param attr KYC attribute data for the client 1636 * @param http_status HTTP status to use 1637 */ 1638 static void 1639 webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh, 1640 enum TALER_KYCLOGIC_KycStatus status, 1641 const char *account_id, 1642 const char *inquiry_id, 1643 const json_t *attr, 1644 unsigned int http_status) 1645 { 1646 struct MHD_Response *resp; 1647 struct GNUNET_TIME_Absolute expiration; 1648 1649 if (TALER_KYCLOGIC_STATUS_SUCCESS == status) 1650 expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity); 1651 else 1652 expiration = GNUNET_TIME_UNIT_ZERO_ABS; 1653 resp = MHD_create_response_from_buffer_static (0, 1654 ""); 1655 TALER_MHD_add_global_headers (resp, 1656 true); 1657 wh->cb (wh->cb_cls, 1658 wh->process_row, 1659 &wh->h_payto, 1660 wh->is_wallet, 1661 wh->pd->provider_name, 1662 account_id, 1663 inquiry_id, 1664 status, 1665 expiration, 1666 attr, 1667 http_status, 1668 resp); 1669 } 1670 1671 1672 /** 1673 * Call @a wh callback with HTTP error response. 1674 * 1675 * @param wh proof handle to generate reply for 1676 * @param inquiry_id inquiry ID to supply, NULL if unknown 1677 * @param http_status HTTP status to use 1678 */ 1679 static void 1680 webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh, 1681 const char *inquiry_id, 1682 unsigned int http_status) 1683 { 1684 webhook_generic_reply (wh, 1685 TALER_KYCLOGIC_STATUS_PROVIDER_FAILED, 1686 NULL, /* user id */ 1687 inquiry_id, 1688 NULL, /* attributes */ 1689 http_status); 1690 } 1691 1692 1693 /** 1694 * Type of a callback that receives a JSON @a result. 1695 * 1696 * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *` 1697 * @param status_type how did the process die 1698 * @param code termination status code from the process 1699 * @param attr some JSON result, NULL if we failed to get an JSON output 1700 */ 1701 static void 1702 webhook_post_conversion_cb (void *cls, 1703 enum GNUNET_OS_ProcessStatusType status_type, 1704 unsigned long code, 1705 const json_t *attr) 1706 { 1707 struct TALER_KYCLOGIC_WebhookHandle *wh = cls; 1708 1709 wh->ec = NULL; 1710 if ( (GNUNET_OS_PROCESS_EXITED != status_type) || 1711 (0 != code) || 1712 (! json_is_string (json_object_get (attr, 1713 "FORM_ID"))) ) 1714 { 1715 struct MHD_Response *resp; 1716 1717 /* Failure in our helper */ 1718 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1719 "Converter died with status %d/%d or " 1720 "mandatory FORM_ID not set in result\n", 1721 (int) status_type, 1722 (int) code); 1723 #if DEBUG 1724 json_dumpf (attr, 1725 stderr, 1726 JSON_INDENT (2)); 1727 #endif 1728 resp = TALER_MHD_MAKE_JSON_PACK ( 1729 GNUNET_JSON_pack_uint64 ("persona_http_status", 1730 wh->persona_http_status), 1731 GNUNET_JSON_pack_object_incref ("persona_body", 1732 (json_t *) attr)); 1733 wh->cb (wh->cb_cls, 1734 wh->process_row, 1735 &wh->h_payto, 1736 wh->is_wallet, 1737 wh->pd->provider_name, 1738 NULL, 1739 wh->inquiry_id, 1740 TALER_KYCLOGIC_STATUS_PROVIDER_FAILED, 1741 GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */ 1742 NULL, 1743 MHD_HTTP_BAD_GATEWAY, 1744 resp); 1745 persona_webhook_cancel (wh); 1746 return; 1747 } 1748 1749 webhook_generic_reply (wh, 1750 TALER_KYCLOGIC_STATUS_SUCCESS, 1751 wh->account_id, 1752 wh->inquiry_id, 1753 attr, 1754 MHD_HTTP_OK); 1755 } 1756 1757 1758 /** 1759 * Function called when we're done processing the 1760 * HTTP "/api/v1/inquiries/{inquiry_id}" request. 1761 * 1762 * @param cls the `struct TALER_KYCLOGIC_WebhookHandle` 1763 * @param response_code HTTP response code, 0 on error 1764 * @param response parsed JSON result, NULL on error 1765 */ 1766 static void 1767 handle_webhook_finished (void *cls, 1768 long response_code, 1769 const void *response) 1770 { 1771 struct TALER_KYCLOGIC_WebhookHandle *wh = cls; 1772 const json_t *j = response; 1773 const json_t *data = json_object_get (j, 1774 "data"); 1775 1776 wh->job = NULL; 1777 wh->persona_http_status = response_code; 1778 switch (response_code) 1779 { 1780 case MHD_HTTP_OK: 1781 { 1782 const char *inquiry_id = NULL; 1783 const char *account_id; 1784 const char *type = NULL; 1785 const json_t *attributes; 1786 const json_t *relationships; 1787 struct GNUNET_JSON_Specification spec[] = { 1788 GNUNET_JSON_spec_string ("type", 1789 &type), 1790 GNUNET_JSON_spec_string ("id", 1791 &inquiry_id), 1792 GNUNET_JSON_spec_object_const ("attributes", 1793 &attributes), 1794 GNUNET_JSON_spec_object_const ("relationships", 1795 &relationships), 1796 GNUNET_JSON_spec_end () 1797 }; 1798 1799 if ( (NULL == data) || 1800 (GNUNET_OK != 1801 GNUNET_JSON_parse (data, 1802 spec, 1803 NULL, NULL)) || 1804 (0 != strcasecmp (type, 1805 "inquiry")) ) 1806 { 1807 GNUNET_break_op (0); 1808 #if DEBUG 1809 json_dumpf (j, 1810 stderr, 1811 JSON_INDENT (2)); 1812 #endif 1813 webhook_reply_error (wh, 1814 inquiry_id, 1815 MHD_HTTP_BAD_GATEWAY); 1816 break; 1817 } 1818 1819 { 1820 const char *status; /* "completed", what else? */ 1821 const char *reference_id; /* or legitimization number */ 1822 const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */ 1823 struct GNUNET_JSON_Specification ispec[] = { 1824 GNUNET_JSON_spec_string ("status", 1825 &status), 1826 GNUNET_JSON_spec_string ("reference-id", 1827 &reference_id), 1828 GNUNET_JSON_spec_mark_optional ( 1829 GNUNET_JSON_spec_string ("expired-at", 1830 &expired_at), 1831 NULL), 1832 GNUNET_JSON_spec_end () 1833 }; 1834 1835 if (GNUNET_OK != 1836 GNUNET_JSON_parse (attributes, 1837 ispec, 1838 NULL, NULL)) 1839 { 1840 GNUNET_break_op (0); 1841 #if DEBUG 1842 json_dumpf (j, 1843 stderr, 1844 JSON_INDENT (2)); 1845 #endif 1846 webhook_reply_error (wh, 1847 inquiry_id, 1848 MHD_HTTP_BAD_GATEWAY); 1849 break; 1850 } 1851 { 1852 unsigned long long idr; 1853 char dummy; 1854 1855 if ( (1 != sscanf (reference_id, 1856 "%llu%c", 1857 &idr, 1858 &dummy)) || 1859 (idr != wh->process_row) ) 1860 { 1861 GNUNET_break_op (0); 1862 webhook_reply_error (wh, 1863 inquiry_id, 1864 MHD_HTTP_BAD_GATEWAY); 1865 break; 1866 } 1867 } 1868 1869 if (0 != strcmp (inquiry_id, 1870 wh->inquiry_id)) 1871 { 1872 GNUNET_break_op (0); 1873 webhook_reply_error (wh, 1874 inquiry_id, 1875 MHD_HTTP_BAD_GATEWAY); 1876 break; 1877 } 1878 1879 account_id = json_string_value ( 1880 json_object_get ( 1881 json_object_get ( 1882 json_object_get ( 1883 relationships, 1884 "account"), 1885 "data"), 1886 "id")); 1887 1888 if (0 != strcasecmp (status, 1889 "completed")) 1890 { 1891 webhook_generic_reply (wh, 1892 TALER_KYCLOGIC_STATUS_FAILED, 1893 account_id, 1894 inquiry_id, 1895 NULL, 1896 MHD_HTTP_OK); 1897 break; 1898 } 1899 1900 if (NULL == account_id) 1901 { 1902 GNUNET_break_op (0); 1903 #if DEBUG 1904 json_dumpf (data, 1905 stderr, 1906 JSON_INDENT (2)); 1907 #endif 1908 webhook_reply_error (wh, 1909 inquiry_id, 1910 MHD_HTTP_BAD_GATEWAY); 1911 break; 1912 } 1913 wh->account_id = GNUNET_strdup (account_id); 1914 wh->ec = start_conversion (wh->pd, 1915 j, 1916 &webhook_post_conversion_cb, 1917 wh); 1918 if (NULL == wh->ec) 1919 { 1920 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1921 "Failed to start Persona conversion helper\n"); 1922 webhook_reply_error (wh, 1923 inquiry_id, 1924 MHD_HTTP_INTERNAL_SERVER_ERROR); 1925 break; 1926 } 1927 } 1928 return; /* continued in webhook_post_conversion_cb */ 1929 } 1930 case MHD_HTTP_BAD_REQUEST: 1931 case MHD_HTTP_NOT_FOUND: 1932 case MHD_HTTP_CONFLICT: 1933 case MHD_HTTP_UNPROCESSABLE_CONTENT: 1934 /* These are errors with this code */ 1935 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1936 "PERSONA failed with response %u:\n", 1937 (unsigned int) response_code); 1938 #if DEBUG 1939 json_dumpf (j, 1940 stderr, 1941 JSON_INDENT (2)); 1942 #endif 1943 webhook_reply_error (wh, 1944 wh->inquiry_id, 1945 MHD_HTTP_BAD_GATEWAY); 1946 break; 1947 case MHD_HTTP_UNAUTHORIZED: 1948 /* These are failures of the exchange operator */ 1949 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1950 "Refused access with HTTP status code %u\n", 1951 (unsigned int) response_code); 1952 webhook_reply_error (wh, 1953 wh->inquiry_id, 1954 MHD_HTTP_INTERNAL_SERVER_ERROR); 1955 break; 1956 case MHD_HTTP_PAYMENT_REQUIRED: 1957 /* These are failures of the exchange operator */ 1958 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1959 "Refused access with HTTP status code %u\n", 1960 (unsigned int) response_code); 1961 1962 webhook_reply_error (wh, 1963 wh->inquiry_id, 1964 MHD_HTTP_INTERNAL_SERVER_ERROR); 1965 break; 1966 case MHD_HTTP_REQUEST_TIMEOUT: 1967 /* These are networking issues */ 1968 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1969 "PERSONA failed with response %u:\n", 1970 (unsigned int) response_code); 1971 #if DEBUG 1972 json_dumpf (j, 1973 stderr, 1974 JSON_INDENT (2)); 1975 #endif 1976 webhook_reply_error (wh, 1977 wh->inquiry_id, 1978 MHD_HTTP_GATEWAY_TIMEOUT); 1979 break; 1980 case MHD_HTTP_TOO_MANY_REQUESTS: 1981 /* This is a load issue */ 1982 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1983 "PERSONA failed with response %u:\n", 1984 (unsigned int) response_code); 1985 #if DEBUG 1986 json_dumpf (j, 1987 stderr, 1988 JSON_INDENT (2)); 1989 #endif 1990 webhook_reply_error (wh, 1991 wh->inquiry_id, 1992 MHD_HTTP_SERVICE_UNAVAILABLE); 1993 break; 1994 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1995 /* This is an issue with Persona */ 1996 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1997 "PERSONA failed with response %u:\n", 1998 (unsigned int) response_code); 1999 #if DEBUG 2000 json_dumpf (j, 2001 stderr, 2002 JSON_INDENT (2)); 2003 #endif 2004 webhook_reply_error (wh, 2005 wh->inquiry_id, 2006 MHD_HTTP_BAD_GATEWAY); 2007 break; 2008 default: 2009 /* This is an issue with Persona */ 2010 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2011 "PERSONA failed with response %u:\n", 2012 (unsigned int) response_code); 2013 #if DEBUG 2014 json_dumpf (j, 2015 stderr, 2016 JSON_INDENT (2)); 2017 #endif 2018 webhook_reply_error (wh, 2019 wh->inquiry_id, 2020 MHD_HTTP_BAD_GATEWAY); 2021 break; 2022 } 2023 2024 persona_webhook_cancel (wh); 2025 } 2026 2027 2028 /** 2029 * Asynchronously return a reply for the webhook. 2030 * 2031 * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *` 2032 */ 2033 static void 2034 async_webhook_reply (void *cls) 2035 { 2036 struct TALER_KYCLOGIC_WebhookHandle *wh = cls; 2037 2038 wh->task = NULL; 2039 wh->cb (wh->cb_cls, 2040 wh->process_row, 2041 (0 == wh->process_row) 2042 ? NULL 2043 : &wh->h_payto, 2044 wh->is_wallet, 2045 (NULL != wh->pd) 2046 ? wh->pd->provider_name 2047 : "<unknown>", 2048 NULL, 2049 wh->inquiry_id, /* provider legi ID */ 2050 TALER_KYCLOGIC_STATUS_PROVIDER_FAILED, 2051 GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */ 2052 NULL, 2053 wh->response_code, 2054 wh->resp); 2055 persona_webhook_cancel (wh); 2056 } 2057 2058 2059 /** 2060 * Function called with the provider details and 2061 * associated plugin closures for matching logics. 2062 * 2063 * @param cls closure 2064 * @param pd provider details of a matching logic 2065 * @param plugin_cls closure of the plugin 2066 * @return #GNUNET_OK to continue to iterate 2067 */ 2068 static enum GNUNET_GenericReturnValue 2069 locate_details_cb ( 2070 void *cls, 2071 const struct TALER_KYCLOGIC_ProviderDetails *pd, 2072 void *plugin_cls) 2073 { 2074 struct TALER_KYCLOGIC_WebhookHandle *wh = cls; 2075 2076 /* This type-checks 'pd' */ 2077 GNUNET_assert (plugin_cls == wh->ps); 2078 if (0 == strcmp (pd->template_id, 2079 wh->template_id)) 2080 { 2081 wh->pd = pd; 2082 return GNUNET_NO; 2083 } 2084 return GNUNET_OK; 2085 } 2086 2087 2088 /** 2089 * Check KYC status and return result for Webhook. We do NOT implement the 2090 * authentication check proposed by the PERSONA documentation, as it would 2091 * allow an attacker who learns the access token to easily bypass the KYC 2092 * checks. Instead, we insist on explicitly requesting the KYC status from the 2093 * provider (at least on success). 2094 * 2095 * @param cls the @e cls of this struct with the plugin-specific state 2096 * @param pd provider configuration details 2097 * @param plc callback to lookup accounts with 2098 * @param plc_cls closure for @a plc 2099 * @param http_method HTTP method used for the webhook 2100 * @param url_path rest of the URL after `/kyc-webhook/` 2101 * @param connection MHD connection object (for HTTP headers) 2102 * @param body HTTP request body 2103 * @param cb function to call with the result 2104 * @param cb_cls closure for @a cb 2105 * @return handle to cancel operation early 2106 */ 2107 static struct TALER_KYCLOGIC_WebhookHandle * 2108 persona_webhook (void *cls, 2109 const struct TALER_KYCLOGIC_ProviderDetails *pd, 2110 TALER_KYCLOGIC_ProviderLookupCallback plc, 2111 void *plc_cls, 2112 const char *http_method, 2113 const char *const url_path[], 2114 struct MHD_Connection *connection, 2115 const json_t *body, 2116 TALER_KYCLOGIC_WebhookCallback cb, 2117 void *cb_cls) 2118 { 2119 struct PluginState *ps = cls; 2120 struct TALER_KYCLOGIC_WebhookHandle *wh; 2121 CURL *eh; 2122 enum GNUNET_DB_QueryStatus qs; 2123 const char *persona_inquiry_id; 2124 const char *auth_header; 2125 bool bad_auth = false; 2126 2127 /* Persona webhooks are expected by logic, not by template */ 2128 GNUNET_break_op (NULL == pd); 2129 wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle); 2130 wh->cb = cb; 2131 wh->cb_cls = cb_cls; 2132 wh->ps = ps; 2133 wh->connection = connection; 2134 wh->pd = pd; 2135 auth_header = MHD_lookup_connection_value (connection, 2136 MHD_HEADER_KIND, 2137 MHD_HTTP_HEADER_AUTHORIZATION); 2138 if (NULL != ps->webhook_token) 2139 { 2140 if (NULL == auth_header) 2141 { 2142 bad_auth = true; 2143 } 2144 else 2145 { 2146 struct GNUNET_HashCode wh_hash; 2147 struct GNUNET_HashCode ah_hash; 2148 2149 /* Compare the shared secret via hashes so that the comparison 2150 time does not leak how many leading bytes of the token 2151 matched (strcmp short-circuits and is a timing oracle). */ 2152 GNUNET_CRYPTO_hash (ps->webhook_token, 2153 strlen (ps->webhook_token), 2154 &wh_hash); 2155 GNUNET_CRYPTO_hash (auth_header, 2156 strlen (auth_header), 2157 &ah_hash); 2158 bad_auth = (0 != GNUNET_memcmp_priv (&wh_hash, 2159 &ah_hash)); 2160 } 2161 } 2162 if (bad_auth) 2163 { 2164 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2165 "Invalid authorization header `%s' received for Persona webhook\n", 2166 auth_header); 2167 wh->resp = TALER_MHD_MAKE_JSON_PACK ( 2168 TALER_JSON_pack_ec ( 2169 TALER_EC_EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED), 2170 GNUNET_JSON_pack_string ("detail", 2171 "unexpected 'Authorization' header")); 2172 wh->response_code = MHD_HTTP_UNAUTHORIZED; 2173 wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, 2174 wh); 2175 return wh; 2176 } 2177 2178 wh->template_id 2179 = json_string_value ( 2180 json_object_get ( 2181 json_object_get ( 2182 json_object_get ( 2183 json_object_get ( 2184 json_object_get ( 2185 json_object_get ( 2186 json_object_get ( 2187 json_object_get ( 2188 body, 2189 "data"), 2190 "attributes"), 2191 "payload"), 2192 "data"), 2193 "relationships"), 2194 "inquiry-template"), 2195 "data"), 2196 "id")); 2197 if (NULL == wh->template_id) 2198 { 2199 GNUNET_break_op (0); 2200 #if DEBUG 2201 json_dumpf (body, 2202 stderr, 2203 JSON_INDENT (2)); 2204 #endif 2205 wh->resp = TALER_MHD_MAKE_JSON_PACK ( 2206 TALER_JSON_pack_ec ( 2207 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY), 2208 GNUNET_JSON_pack_string ("detail", 2209 "data-attributes-payload-data-id"), 2210 GNUNET_JSON_pack_object_incref ("webhook_body", 2211 (json_t *) body)); 2212 wh->response_code = MHD_HTTP_BAD_REQUEST; 2213 wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, 2214 wh); 2215 return wh; 2216 } 2217 TALER_KYCLOGIC_kyc_get_details ("persona", 2218 &locate_details_cb, 2219 wh); 2220 if (NULL == wh->pd) 2221 { 2222 GNUNET_break_op (0); 2223 #if DEBUG 2224 json_dumpf (body, 2225 stderr, 2226 JSON_INDENT (2)); 2227 #endif 2228 wh->resp = TALER_MHD_MAKE_JSON_PACK ( 2229 TALER_JSON_pack_ec ( 2230 TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN), 2231 GNUNET_JSON_pack_string ("detail", 2232 wh->template_id), 2233 GNUNET_JSON_pack_object_incref ("webhook_body", 2234 (json_t *) body)); 2235 wh->response_code = MHD_HTTP_BAD_REQUEST; 2236 wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, 2237 wh); 2238 return wh; 2239 } 2240 2241 persona_inquiry_id 2242 = json_string_value ( 2243 json_object_get ( 2244 json_object_get ( 2245 json_object_get ( 2246 json_object_get ( 2247 json_object_get ( 2248 body, 2249 "data"), 2250 "attributes"), 2251 "payload"), 2252 "data"), 2253 "id")); 2254 if (NULL == persona_inquiry_id) 2255 { 2256 GNUNET_break_op (0); 2257 #if DEBUG 2258 json_dumpf (body, 2259 stderr, 2260 JSON_INDENT (2)); 2261 #endif 2262 wh->resp = TALER_MHD_MAKE_JSON_PACK ( 2263 TALER_JSON_pack_ec ( 2264 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY), 2265 GNUNET_JSON_pack_string ("detail", 2266 "data-attributes-payload-data-id"), 2267 GNUNET_JSON_pack_object_incref ("webhook_body", 2268 (json_t *) body)); 2269 wh->response_code = MHD_HTTP_BAD_REQUEST; 2270 wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, 2271 wh); 2272 return wh; 2273 } 2274 qs = plc (plc_cls, 2275 wh->pd->provider_name, 2276 persona_inquiry_id, 2277 &wh->h_payto, 2278 &wh->is_wallet, 2279 &wh->process_row); 2280 if (qs < 0) 2281 { 2282 wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, 2283 "provider-legitimization-lookup"); 2284 wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 2285 wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, 2286 wh); 2287 return wh; 2288 } 2289 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 2290 { 2291 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2292 "Received Persona kyc-webhook for unknown verification ID `%s'\n", 2293 persona_inquiry_id); 2294 wh->resp = TALER_MHD_make_error ( 2295 TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN, 2296 persona_inquiry_id); 2297 wh->response_code = MHD_HTTP_NOT_FOUND; 2298 wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, 2299 wh); 2300 return wh; 2301 } 2302 wh->inquiry_id = GNUNET_strdup (persona_inquiry_id); 2303 2304 eh = curl_easy_init (); 2305 if (NULL == eh) 2306 { 2307 GNUNET_break (0); 2308 wh->resp = TALER_MHD_make_error ( 2309 TALER_EC_GENERIC_ALLOCATION_FAILURE, 2310 NULL); 2311 wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 2312 wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, 2313 wh); 2314 return wh; 2315 } 2316 2317 { 2318 char *inquiry_id_encoded; 2319 2320 inquiry_id_encoded = TALER_urlencode (persona_inquiry_id); 2321 GNUNET_asprintf (&wh->url, 2322 "https://withpersona.com/api/v1/inquiries/%s", 2323 inquiry_id_encoded); 2324 GNUNET_free (inquiry_id_encoded); 2325 } 2326 GNUNET_break (CURLE_OK == 2327 curl_easy_setopt (eh, 2328 CURLOPT_VERBOSE, 2329 0)); 2330 GNUNET_assert (CURLE_OK == 2331 curl_easy_setopt (eh, 2332 CURLOPT_MAXREDIRS, 2333 1L)); 2334 GNUNET_break (CURLE_OK == 2335 curl_easy_setopt (eh, 2336 CURLOPT_URL, 2337 wh->url)); 2338 wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx, 2339 eh, 2340 wh->pd->slist, 2341 &handle_webhook_finished, 2342 wh); 2343 return wh; 2344 } 2345 2346 2347 /** 2348 * Initialize persona logic plugin 2349 * 2350 * @param cls a configuration instance 2351 * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin` 2352 */ 2353 void * 2354 libtaler_plugin_kyclogic_persona_init (void *cls); 2355 2356 /* declaration to avoid compiler warning */ 2357 void * 2358 libtaler_plugin_kyclogic_persona_init (void *cls) 2359 { 2360 const struct GNUNET_CONFIGURATION_Handle *cfg = cls; 2361 struct TALER_KYCLOGIC_Plugin *plugin; 2362 struct PluginState *ps; 2363 2364 ps = GNUNET_new (struct PluginState); 2365 ps->cfg = cfg; 2366 if (GNUNET_OK != 2367 GNUNET_CONFIGURATION_get_value_string (cfg, 2368 "exchange", 2369 "BASE_URL", 2370 &ps->exchange_base_url)) 2371 { 2372 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 2373 "exchange", 2374 "BASE_URL"); 2375 GNUNET_free (ps); 2376 return NULL; 2377 } 2378 if (GNUNET_OK != 2379 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 2380 "kyclogic-persona", 2381 "WEBHOOK_AUTH_TOKEN", 2382 &ps->webhook_token)) 2383 { 2384 /* optional */ 2385 ps->webhook_token = NULL; 2386 } 2387 2388 ps->curl_ctx 2389 = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 2390 &ps->curl_rc); 2391 if (NULL == ps->curl_ctx) 2392 { 2393 GNUNET_break (0); 2394 GNUNET_free (ps->exchange_base_url); 2395 GNUNET_free (ps); 2396 return NULL; 2397 } 2398 ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx); 2399 2400 plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin); 2401 plugin->cls = ps; 2402 plugin->load_configuration 2403 = &persona_load_configuration; 2404 plugin->unload_configuration 2405 = &persona_unload_configuration; 2406 plugin->initiate 2407 = &persona_initiate; 2408 plugin->initiate_cancel 2409 = &persona_initiate_cancel; 2410 plugin->proof 2411 = &persona_proof; 2412 plugin->proof_cancel 2413 = &persona_proof_cancel; 2414 plugin->webhook 2415 = &persona_webhook; 2416 plugin->webhook_cancel 2417 = &persona_webhook_cancel; 2418 return plugin; 2419 } 2420 2421 2422 /** 2423 * Unload authorization plugin 2424 * 2425 * @param cls a `struct TALER_KYCLOGIC_Plugin` 2426 * @return NULL (always) 2427 */ 2428 void * 2429 libtaler_plugin_kyclogic_persona_done (void *cls); 2430 2431 /* declaration to avoid compiler warning */ 2432 2433 void * 2434 libtaler_plugin_kyclogic_persona_done (void *cls) 2435 { 2436 struct TALER_KYCLOGIC_Plugin *plugin = cls; 2437 struct PluginState *ps = plugin->cls; 2438 2439 if (NULL != ps->curl_ctx) 2440 { 2441 GNUNET_CURL_fini (ps->curl_ctx); 2442 ps->curl_ctx = NULL; 2443 } 2444 if (NULL != ps->curl_rc) 2445 { 2446 GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc); 2447 ps->curl_rc = NULL; 2448 } 2449 GNUNET_free (ps->exchange_base_url); 2450 GNUNET_free (ps->webhook_token); 2451 GNUNET_free (ps); 2452 GNUNET_free (plugin); 2453 return NULL; 2454 } 2455 2456 2457 /* end of plugin_kyclogic_persona.c */