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