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