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