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