plugin_kyclogic_oauth2.c (60335B)
1 /* 2 This file is part of GNU Taler 3 Copyright (C) 2022-2024 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_oauth2.c 18 * @brief oauth2.0 based 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_templating_lib.h" 24 #include "taler/taler_curl_lib.h" 25 #include "taler/taler_json_lib.h" 26 #include <regex.h> 27 #include "taler/taler_util.h" 28 29 /** 30 * Set to 1 to get extra-verbose, possibly privacy-sensitive 31 * data in the logs. 32 */ 33 #define DEBUG 0 34 35 /** 36 * Saves the state of a plugin. 37 */ 38 struct PluginState 39 { 40 41 /** 42 * Our global configuration. 43 */ 44 const struct GNUNET_CONFIGURATION_Handle *cfg; 45 46 /** 47 * Our base URL. 48 */ 49 char *exchange_base_url; 50 51 /** 52 * Context for CURL operations (useful to the event loop) 53 */ 54 struct GNUNET_CURL_Context *curl_ctx; 55 56 /** 57 * Context for integrating @e curl_ctx with the 58 * GNUnet event loop. 59 */ 60 struct GNUNET_CURL_RescheduleContext *curl_rc; 61 62 }; 63 64 65 /** 66 * Keeps the plugin-specific state for 67 * a given configuration section. 68 */ 69 struct TALER_KYCLOGIC_ProviderDetails 70 { 71 72 /** 73 * Overall plugin state. 74 */ 75 struct PluginState *ps; 76 77 /** 78 * Configuration section that configured us. 79 */ 80 char *section; 81 82 /** 83 * URL of the Challenger ``/setup`` endpoint for 84 * approving address validations. NULL if not used. 85 */ 86 char *setup_url; 87 88 /** 89 * URL of the OAuth2.0 endpoint for KYC checks. 90 */ 91 char *authorize_url; 92 93 /** 94 * URL of the OAuth2.0 endpoint for KYC checks. 95 * (token/auth) 96 */ 97 char *token_url; 98 99 /** 100 * URL of the user info access endpoint. 101 */ 102 char *info_url; 103 104 /** 105 * Our client ID for OAuth2.0. 106 */ 107 char *client_id; 108 109 /** 110 * Our client secret for OAuth2.0. 111 */ 112 char *client_secret; 113 114 /** 115 * OAuth2 scope, NULL if not used 116 */ 117 char *scope; 118 119 /** 120 * Where to redirect clients after the 121 * Web-based KYC process is done? 122 */ 123 char *post_kyc_redirect_url; 124 125 /** 126 * Name of the program we use to convert outputs 127 * from OAuth2 outputs into our JSON inputs. 128 */ 129 char *conversion_binary; 130 131 /** 132 * Validity time for a successful KYC process. 133 */ 134 struct GNUNET_TIME_Relative validity; 135 136 /** 137 * Set to true if we are operating in DEBUG 138 * mode and may return private details in HTML 139 * responses to make diagnostics easier. 140 */ 141 bool debug_mode; 142 }; 143 144 145 /** 146 * Handle for an initiation operation. 147 */ 148 struct TALER_KYCLOGIC_InitiateHandle 149 { 150 151 /** 152 * Hash of the payto:// URI we are initiating 153 * the KYC for. 154 */ 155 struct TALER_NormalizedPaytoHashP h_payto; 156 157 /** 158 * UUID being checked. 159 */ 160 uint64_t legitimization_uuid; 161 162 /** 163 * Our configuration details. 164 */ 165 const struct TALER_KYCLOGIC_ProviderDetails *pd; 166 167 /** 168 * The task for asynchronous response generation. 169 */ 170 struct GNUNET_SCHEDULER_Task *task; 171 172 /** 173 * Handle for the OAuth 2.0 setup request. 174 */ 175 struct GNUNET_CURL_Job *job; 176 177 /** 178 * Continuation to call. 179 */ 180 TALER_KYCLOGIC_InitiateCallback cb; 181 182 /** 183 * Closure for @a cb. 184 */ 185 void *cb_cls; 186 187 /** 188 * Initial address to pass to the KYC provider on ``/setup``. 189 */ 190 json_t *initial_address; 191 192 /** 193 * Expiration reported by a dynamic /setup endpoint. 194 */ 195 struct GNUNET_TIME_Timestamp process_expiration; 196 197 /** 198 * Context for #TEH_curl_easy_post(). Keeps the data that must 199 * persist for Curl to make the upload. 200 */ 201 struct TALER_CURL_PostContext ctx; 202 203 }; 204 205 206 /** 207 * Handle for an KYC proof operation. 208 */ 209 struct TALER_KYCLOGIC_ProofHandle 210 { 211 212 /** 213 * Our configuration details. 214 */ 215 const struct TALER_KYCLOGIC_ProviderDetails *pd; 216 217 /** 218 * HTTP connection we are processing. 219 */ 220 struct MHD_Connection *connection; 221 222 /** 223 * Handle to an external process that converts the 224 * Persona response to our internal format. 225 */ 226 struct TALER_JSON_ExternalConversion *ec; 227 228 /** 229 * Hash of the payto URI that this is about. 230 */ 231 struct TALER_NormalizedPaytoHashP h_payto; 232 233 /** 234 * Continuation to call. 235 */ 236 TALER_KYCLOGIC_ProofCallback cb; 237 238 /** 239 * Closure for @e cb. 240 */ 241 void *cb_cls; 242 243 /** 244 * Curl request we are running to the OAuth 2.0 service. 245 */ 246 CURL *eh; 247 248 /** 249 * Body for the @e eh POST request. 250 */ 251 char *post_body; 252 253 /** 254 * KYC attributes returned about the user by the OAuth 2.0 server. 255 */ 256 json_t *attributes; 257 258 /** 259 * Response to return. 260 */ 261 struct MHD_Response *response; 262 263 /** 264 * The task for asynchronous response generation. 265 */ 266 struct GNUNET_SCHEDULER_Task *task; 267 268 /** 269 * Handle for the OAuth 2.0 CURL request. 270 */ 271 struct GNUNET_CURL_Job *job; 272 273 /** 274 * User ID to return, the 'id' from OAuth. 275 */ 276 char *provider_user_id; 277 278 /** 279 * Legitimization ID to return, the 64-bit row ID 280 * as a string. 281 */ 282 char provider_legitimization_id[32]; 283 284 /** 285 * KYC status to return. 286 */ 287 enum TALER_KYCLOGIC_KycStatus status; 288 289 /** 290 * HTTP status to return. 291 */ 292 unsigned int http_status; 293 294 295 }; 296 297 298 /** 299 * Handle for an KYC Web hook operation. 300 */ 301 struct TALER_KYCLOGIC_WebhookHandle 302 { 303 304 /** 305 * Continuation to call when done. 306 */ 307 TALER_KYCLOGIC_WebhookCallback cb; 308 309 /** 310 * Closure for @a cb. 311 */ 312 void *cb_cls; 313 314 /** 315 * Task for asynchronous execution. 316 */ 317 struct GNUNET_SCHEDULER_Task *task; 318 319 /** 320 * Overall plugin state. 321 */ 322 struct PluginState *ps; 323 }; 324 325 326 /** 327 * Release configuration resources previously loaded 328 * 329 * @param[in] pd configuration to release 330 */ 331 static void 332 oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) 333 { 334 GNUNET_free (pd->section); 335 GNUNET_free (pd->token_url); 336 GNUNET_free (pd->setup_url); 337 GNUNET_free (pd->authorize_url); 338 GNUNET_free (pd->info_url); 339 GNUNET_free (pd->client_id); 340 GNUNET_free (pd->client_secret); 341 GNUNET_free (pd->scope); 342 GNUNET_free (pd->post_kyc_redirect_url); 343 GNUNET_free (pd->conversion_binary); 344 GNUNET_free (pd); 345 } 346 347 348 /** 349 * Load the configuration of the KYC provider. 350 * 351 * @param cls closure 352 * @param provider_section_name configuration section to parse 353 * @return NULL if configuration is invalid 354 */ 355 static struct TALER_KYCLOGIC_ProviderDetails * 356 oauth2_load_configuration (void *cls, 357 const char *provider_section_name) 358 { 359 struct PluginState *ps = cls; 360 struct TALER_KYCLOGIC_ProviderDetails *pd; 361 char *s; 362 363 pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails); 364 pd->ps = ps; 365 pd->section = GNUNET_strdup (provider_section_name); 366 if (GNUNET_OK != 367 GNUNET_CONFIGURATION_get_value_time (ps->cfg, 368 provider_section_name, 369 "KYC_OAUTH2_VALIDITY", 370 &pd->validity)) 371 { 372 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 373 provider_section_name, 374 "KYC_OAUTH2_VALIDITY"); 375 oauth2_unload_configuration (pd); 376 return NULL; 377 } 378 379 if (GNUNET_OK != 380 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 381 provider_section_name, 382 "KYC_OAUTH2_CLIENT_ID", 383 &s)) 384 { 385 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 386 provider_section_name, 387 "KYC_OAUTH2_CLIENT_ID"); 388 oauth2_unload_configuration (pd); 389 return NULL; 390 } 391 pd->client_id = s; 392 393 if (GNUNET_OK == 394 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 395 provider_section_name, 396 "KYC_OAUTH2_SCOPE", 397 &s)) 398 { 399 pd->scope = s; 400 } 401 402 if (GNUNET_OK != 403 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 404 provider_section_name, 405 "KYC_OAUTH2_TOKEN_URL", 406 &s)) 407 { 408 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 409 provider_section_name, 410 "KYC_OAUTH2_TOKEN_URL"); 411 oauth2_unload_configuration (pd); 412 return NULL; 413 } 414 if ( (! TALER_url_valid_charset (s)) || 415 ( (0 != strncasecmp (s, 416 "http://", 417 strlen ("http://"))) && 418 (0 != strncasecmp (s, 419 "https://", 420 strlen ("https://"))) ) ) 421 { 422 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 423 provider_section_name, 424 "KYC_OAUTH2_TOKEN_URL", 425 "not a valid URL"); 426 GNUNET_free (s); 427 oauth2_unload_configuration (pd); 428 return NULL; 429 } 430 pd->token_url = s; 431 432 if (GNUNET_OK != 433 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 434 provider_section_name, 435 "KYC_OAUTH2_AUTHORIZE_URL", 436 &s)) 437 { 438 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 439 provider_section_name, 440 "KYC_OAUTH2_AUTHORIZE_URL"); 441 oauth2_unload_configuration (pd); 442 return NULL; 443 } 444 if ( (! TALER_url_valid_charset (s)) || 445 ( (0 != strncasecmp (s, 446 "http://", 447 strlen ("http://"))) && 448 (0 != strncasecmp (s, 449 "https://", 450 strlen ("https://"))) ) ) 451 { 452 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 453 provider_section_name, 454 "KYC_OAUTH2_AUTHORIZE_URL", 455 "not a valid URL"); 456 oauth2_unload_configuration (pd); 457 GNUNET_free (s); 458 return NULL; 459 } 460 if (NULL != strchr (s, '#')) 461 { 462 const char *extra = strchr (s, '#'); 463 const char *slash = strrchr (s, '/'); 464 465 if ( (0 != strcasecmp (extra, 466 "#setup")) || 467 (NULL == slash) ) 468 { 469 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 470 provider_section_name, 471 "KYC_OAUTH2_AUTHORIZE_URL", 472 "not a valid authorze URL (bad fragment)"); 473 oauth2_unload_configuration (pd); 474 GNUNET_free (s); 475 return NULL; 476 } 477 pd->authorize_url = GNUNET_strndup (s, 478 extra - s); 479 GNUNET_asprintf (&pd->setup_url, 480 "%.*s/setup/%s", 481 (int) (slash - s), 482 s, 483 pd->client_id); 484 GNUNET_free (s); 485 } 486 else 487 { 488 pd->authorize_url = s; 489 } 490 491 if (GNUNET_OK != 492 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 493 provider_section_name, 494 "KYC_OAUTH2_INFO_URL", 495 &s)) 496 { 497 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 498 provider_section_name, 499 "KYC_OAUTH2_INFO_URL"); 500 oauth2_unload_configuration (pd); 501 return NULL; 502 } 503 if ( (! TALER_url_valid_charset (s)) || 504 ( (0 != strncasecmp (s, 505 "http://", 506 strlen ("http://"))) && 507 (0 != strncasecmp (s, 508 "https://", 509 strlen ("https://"))) ) ) 510 { 511 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 512 provider_section_name, 513 "KYC_INFO_URL", 514 "not a valid URL"); 515 GNUNET_free (s); 516 oauth2_unload_configuration (pd); 517 return NULL; 518 } 519 pd->info_url = s; 520 521 if (GNUNET_OK != 522 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 523 provider_section_name, 524 "KYC_OAUTH2_CLIENT_SECRET", 525 &s)) 526 { 527 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 528 provider_section_name, 529 "KYC_OAUTH2_CLIENT_SECRET"); 530 oauth2_unload_configuration (pd); 531 return NULL; 532 } 533 pd->client_secret = s; 534 535 if (GNUNET_OK != 536 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 537 provider_section_name, 538 "KYC_OAUTH2_POST_URL", 539 &s)) 540 { 541 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 542 provider_section_name, 543 "KYC_OAUTH2_POST_URL"); 544 oauth2_unload_configuration (pd); 545 return NULL; 546 } 547 pd->post_kyc_redirect_url = s; 548 549 if (GNUNET_OK != 550 GNUNET_CONFIGURATION_get_value_string (ps->cfg, 551 provider_section_name, 552 "KYC_OAUTH2_CONVERTER_HELPER", 553 &pd->conversion_binary)) 554 { 555 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 556 provider_section_name, 557 "KYC_OAUTH2_CONVERTER_HELPER"); 558 oauth2_unload_configuration (pd); 559 return NULL; 560 } 561 if (GNUNET_OK == 562 GNUNET_CONFIGURATION_get_value_yesno (ps->cfg, 563 provider_section_name, 564 "KYC_OAUTH2_DEBUG_MODE")) 565 pd->debug_mode = true; 566 567 return pd; 568 } 569 570 571 /** 572 * Cancel KYC check initiation. 573 * 574 * @param[in] ih handle of operation to cancel 575 */ 576 static void 577 oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih) 578 { 579 if (NULL != ih->task) 580 { 581 GNUNET_SCHEDULER_cancel (ih->task); 582 ih->task = NULL; 583 } 584 if (NULL != ih->job) 585 { 586 GNUNET_CURL_job_cancel (ih->job); 587 ih->job = NULL; 588 } 589 TALER_curl_easy_post_finished (&ih->ctx); 590 json_decref (ih->initial_address); 591 GNUNET_free (ih); 592 } 593 594 595 /** 596 * Logic to asynchronously return the response for 597 * how to begin the OAuth2.0 checking process to 598 * the client. 599 * 600 * @param ih process to redirect for 601 * @param authorize_url authorization URL to use 602 */ 603 static void 604 initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih, 605 const char *authorize_url) 606 { 607 608 const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; 609 struct PluginState *ps = pd->ps; 610 char *hps; 611 char *url; 612 char legi_s[42]; 613 614 GNUNET_snprintf (legi_s, 615 sizeof (legi_s), 616 "%llu", 617 (unsigned long long) ih->legitimization_uuid); 618 hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto, 619 sizeof (ih->h_payto)); 620 { 621 char *redirect_uri_encoded; 622 char *client_id_encoded; 623 char *scope_encoded; 624 625 { 626 char *redirect_uri; 627 628 GNUNET_asprintf (&redirect_uri, 629 "%skyc-proof/%s", 630 ps->exchange_base_url, 631 &pd->section[strlen ("kyc-provider-")]); 632 redirect_uri_encoded = TALER_urlencode (redirect_uri); 633 GNUNET_free (redirect_uri); 634 } 635 client_id_encoded = TALER_urlencode (pd->client_id); 636 scope_encoded = TALER_urlencode (NULL != pd->scope 637 ? pd->scope 638 : ""); 639 GNUNET_asprintf (&url, 640 "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s&scope=%s", 641 authorize_url, 642 client_id_encoded, 643 redirect_uri_encoded, 644 hps, 645 scope_encoded); 646 GNUNET_free (scope_encoded); 647 GNUNET_free (client_id_encoded); 648 GNUNET_free (redirect_uri_encoded); 649 } 650 ih->cb (ih->cb_cls, 651 TALER_EC_NONE, 652 url, 653 NULL /* unknown user_id here */, 654 legi_s, 655 NULL /* no error */); 656 GNUNET_free (url); 657 GNUNET_free (hps); 658 oauth2_initiate_cancel (ih); 659 } 660 661 662 /** 663 * After we are done with the CURL interaction we 664 * need to update our database state with the information 665 * retrieved. 666 * 667 * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` 668 * @param response_code HTTP response code from server, 0 on hard error 669 * @param response in JSON, NULL if response was not in JSON format 670 */ 671 static void 672 handle_curl_setup_finished (void *cls, 673 long response_code, 674 const void *response) 675 { 676 struct TALER_KYCLOGIC_InitiateHandle *ih = cls; 677 const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; 678 const json_t *j = response; 679 680 ih->job = NULL; 681 switch (response_code) 682 { 683 case 0: 684 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 685 "/setup URL failed to return HTTP response\n"); 686 ih->cb (ih->cb_cls, 687 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, 688 NULL, 689 NULL, 690 NULL, 691 "/setup request to OAuth 2.0 backend returned no response"); 692 oauth2_initiate_cancel (ih); 693 return; 694 case MHD_HTTP_OK: 695 { 696 const char *nonce; 697 bool no_expiration; 698 struct GNUNET_JSON_Specification spec[] = { 699 GNUNET_JSON_spec_string ("nonce", 700 &nonce), 701 GNUNET_JSON_spec_mark_optional ( 702 GNUNET_JSON_spec_timestamp ("expires", 703 &ih->process_expiration), 704 &no_expiration), 705 GNUNET_JSON_spec_end () 706 }; 707 enum GNUNET_GenericReturnValue res; 708 const char *emsg; 709 unsigned int line; 710 char *url; 711 712 res = GNUNET_JSON_parse (j, 713 spec, 714 &emsg, 715 &line); 716 if (GNUNET_OK != res) 717 { 718 GNUNET_break_op (0); 719 json_dumpf (j, 720 stderr, 721 JSON_INDENT (2)); 722 ih->cb (ih->cb_cls, 723 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, 724 NULL, 725 NULL, 726 NULL, 727 "Unexpected response from KYC gateway: setup must return a nonce"); 728 oauth2_initiate_cancel (ih); 729 return; 730 } 731 if ( (! no_expiration) && 732 GNUNET_TIME_timestamp_cmp ( 733 ih->process_expiration, 734 <=, 735 GNUNET_TIME_timestamp_get ()) ) 736 { 737 GNUNET_break_op (0); 738 ih->cb (ih->cb_cls, 739 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, 740 NULL, 741 NULL, 742 NULL, 743 "KYC gateway returned an expired setup process"); 744 GNUNET_JSON_parse_free (spec); 745 oauth2_initiate_cancel (ih); 746 return; 747 } 748 { 749 char *nonce_encoded; 750 751 nonce_encoded = TALER_urlencode (nonce); 752 GNUNET_asprintf (&url, 753 "%s/%s", 754 pd->authorize_url, 755 nonce_encoded); 756 GNUNET_free (nonce_encoded); 757 } 758 initiate_with_url (ih, 759 url); 760 GNUNET_free (url); 761 return; 762 } 763 break; 764 default: 765 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 766 "/setup URL returned HTTP status %u\n", 767 (unsigned int) response_code); 768 ih->cb (ih->cb_cls, 769 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, 770 NULL, 771 NULL, 772 NULL, 773 "/setup request to OAuth 2.0 backend returned unexpected HTTP status code"); 774 oauth2_initiate_cancel (ih); 775 return; 776 } 777 } 778 779 780 static struct GNUNET_TIME_Timestamp 781 oauth2_initiate_get_expiration ( 782 const struct TALER_KYCLOGIC_InitiateHandle *ih) 783 { 784 return ih->process_expiration; 785 } 786 787 788 /** 789 * Logic to asynchronously return the response for how to begin the OAuth2.0 790 * checking process to the client. May first request a dynamic URL via 791 * ``/setup`` if configured to use a client-authenticated setup process. 792 * 793 * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` 794 */ 795 static void 796 initiate_task (void *cls) 797 { 798 struct TALER_KYCLOGIC_InitiateHandle *ih = cls; 799 const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; 800 struct PluginState *ps = pd->ps; 801 CURL *eh; 802 803 ih->task = NULL; 804 if (NULL == pd->setup_url) 805 { 806 initiate_with_url (ih, 807 pd->authorize_url); 808 return; 809 } 810 eh = curl_easy_init (); 811 if (NULL == eh) 812 { 813 GNUNET_break (0); 814 ih->cb (ih->cb_cls, 815 TALER_EC_GENERIC_ALLOCATION_FAILURE, 816 NULL, 817 NULL, 818 NULL, 819 "curl_easy_init() failed"); 820 oauth2_initiate_cancel (ih); 821 return; 822 } 823 GNUNET_assert (CURLE_OK == 824 curl_easy_setopt (eh, 825 CURLOPT_URL, 826 pd->setup_url)); 827 #if DEBUG 828 GNUNET_assert (CURLE_OK == 829 curl_easy_setopt (eh, 830 CURLOPT_VERBOSE, 831 1)); 832 #endif 833 if (NULL == ih->initial_address) 834 { 835 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 836 "Staring OAuth 2.0 without initial address\n"); 837 GNUNET_assert (CURLE_OK == 838 curl_easy_setopt (eh, 839 CURLOPT_POST, 840 1)); 841 GNUNET_assert (CURLE_OK == 842 curl_easy_setopt (eh, 843 CURLOPT_POSTFIELDS, 844 "")); 845 GNUNET_assert (CURLE_OK == 846 curl_easy_setopt (eh, 847 CURLOPT_POSTFIELDSIZE, 848 (long) 0)); 849 } 850 else 851 { 852 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 853 "Staring OAuth 2.0 with initial address\n"); 854 #if DEBUG 855 json_dumpf (ih->initial_address, 856 stderr, 857 JSON_INDENT (2)); 858 fprintf (stderr, 859 "\n"); 860 #endif 861 if (GNUNET_OK != 862 TALER_curl_easy_post (&ih->ctx, 863 eh, 864 ih->initial_address)) 865 { 866 curl_easy_cleanup (eh); 867 ih->cb (ih->cb_cls, 868 TALER_EC_GENERIC_ALLOCATION_FAILURE, 869 NULL, 870 NULL, 871 NULL, 872 "TALER_curl_easy_post() failed"); 873 oauth2_initiate_cancel (ih); 874 return; 875 } 876 } 877 GNUNET_assert (CURLE_OK == 878 curl_easy_setopt (eh, 879 CURLOPT_FOLLOWLOCATION, 880 1L)); 881 GNUNET_assert (CURLE_OK == 882 curl_easy_setopt (eh, 883 CURLOPT_MAXREDIRS, 884 5L)); 885 ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx, 886 eh, 887 ih->ctx.headers, 888 &handle_curl_setup_finished, 889 ih); 890 { 891 char *hdr; 892 struct curl_slist *slist; 893 894 GNUNET_asprintf (&hdr, 895 "%s: Bearer %s", 896 MHD_HTTP_HEADER_AUTHORIZATION, 897 pd->client_secret); 898 slist = curl_slist_append (NULL, 899 hdr); 900 GNUNET_CURL_extend_headers (ih->job, 901 slist); 902 curl_slist_free_all (slist); 903 GNUNET_free (hdr); 904 } 905 } 906 907 908 /** 909 * Initiate KYC check. 910 * 911 * @param cls the @e cls of this struct with the plugin-specific state 912 * @param pd provider configuration details 913 * @param account_id which account to trigger process for 914 * @param legitimization_uuid unique ID for the legitimization process 915 * @param context additional contextual information for the legi process 916 * @param cb function to call with the result 917 * @param cb_cls closure for @a cb 918 * @return handle to cancel operation early 919 */ 920 static struct TALER_KYCLOGIC_InitiateHandle * 921 oauth2_initiate (void *cls, 922 const struct TALER_KYCLOGIC_ProviderDetails *pd, 923 const struct TALER_NormalizedPaytoHashP *account_id, 924 uint64_t legitimization_uuid, 925 const json_t *context, 926 TALER_KYCLOGIC_InitiateCallback cb, 927 void *cb_cls) 928 { 929 struct TALER_KYCLOGIC_InitiateHandle *ih; 930 931 (void) cls; 932 ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle); 933 ih->legitimization_uuid = legitimization_uuid; 934 ih->cb = cb; 935 ih->cb_cls = cb_cls; 936 ih->h_payto = *account_id; 937 ih->pd = pd; 938 ih->task = GNUNET_SCHEDULER_add_now (&initiate_task, 939 ih); 940 if (NULL != context) 941 { 942 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 943 "Initiating OAuth2 validation with context\n"); 944 #if DEBUG 945 json_dumpf (context, 946 stderr, 947 JSON_INDENT (2)); 948 fprintf (stderr, 949 "\n"); 950 #endif 951 ih->initial_address = json_incref (json_object_get (context, 952 "initial_address")); 953 } 954 return ih; 955 } 956 957 958 /** 959 * Cancel KYC proof. 960 * 961 * @param[in] ph handle of operation to cancel 962 */ 963 static void 964 oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) 965 { 966 if (NULL != ph->ec) 967 { 968 TALER_JSON_external_conversion_stop (ph->ec); 969 ph->ec = NULL; 970 } 971 if (NULL != ph->task) 972 { 973 GNUNET_SCHEDULER_cancel (ph->task); 974 ph->task = NULL; 975 } 976 if (NULL != ph->job) 977 { 978 GNUNET_CURL_job_cancel (ph->job); 979 ph->job = NULL; 980 } 981 if (NULL != ph->response) 982 { 983 MHD_destroy_response (ph->response); 984 ph->response = NULL; 985 } 986 GNUNET_free (ph->provider_user_id); 987 if (NULL != ph->attributes) 988 json_decref (ph->attributes); 989 GNUNET_free (ph->post_body); 990 GNUNET_free (ph); 991 } 992 993 994 /** 995 * Function called to asynchronously return the final 996 * result to the callback. 997 * 998 * @param cls a `struct TALER_KYCLOGIC_ProofHandle` 999 */ 1000 static void 1001 return_proof_response (void *cls) 1002 { 1003 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1004 const char *provider_name; 1005 1006 ph->task = NULL; 1007 provider_name = ph->pd->section; 1008 if (0 != 1009 strncasecmp (provider_name, 1010 "KYC-PROVIDER-", 1011 strlen ("KYC-PROVIDER-"))) 1012 { 1013 GNUNET_break (0); 1014 } 1015 else 1016 { 1017 provider_name += strlen ("KYC-PROVIDER-"); 1018 } 1019 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1020 "Returning KYC proof from `%s'\n", 1021 provider_name); 1022 ph->cb (ph->cb_cls, 1023 ph->status, 1024 provider_name, 1025 ph->provider_user_id, 1026 ph->provider_legitimization_id, 1027 GNUNET_TIME_relative_to_absolute (ph->pd->validity), 1028 ph->attributes, 1029 ph->http_status, 1030 ph->response); 1031 ph->response = NULL; /*Ownership passed to 'ph->cb'!*/ 1032 oauth2_proof_cancel (ph); 1033 } 1034 1035 1036 /** 1037 * Load a @a template and substitute using @a root, returning the result in a 1038 * @a reply encoded suitable for the @a connection with the given @a 1039 * http_status code. On errors, the @a http_status code 1040 * is updated to reflect the type of error encoded in the 1041 * @a reply. 1042 * 1043 * @param connection the connection we act upon 1044 * @param[in,out] http_status code to use on success, 1045 * set to alternative code on failure 1046 * @param template basename of the template to load 1047 * @param root JSON object to pass as the root context 1048 * @param[out] reply where to write the response object 1049 * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued, 1050 * #GNUNET_SYSERR on failure (to queue an error) 1051 */ 1052 static enum GNUNET_GenericReturnValue 1053 templating_build (struct MHD_Connection *connection, 1054 unsigned int *http_status, 1055 const char *template, 1056 const json_t *root, 1057 struct MHD_Response **reply) 1058 { 1059 enum GNUNET_GenericReturnValue ret; 1060 1061 ret = TALER_TEMPLATING_build (connection, 1062 http_status, 1063 template, 1064 NULL, 1065 NULL, 1066 root, 1067 reply); 1068 if (GNUNET_SYSERR != ret) 1069 { 1070 GNUNET_break (MHD_NO != 1071 MHD_add_response_header (*reply, 1072 MHD_HTTP_HEADER_CONTENT_TYPE, 1073 "text/html")); 1074 } 1075 return ret; 1076 } 1077 1078 1079 /** 1080 * The request for @a ph failed. We may have gotten a useful error 1081 * message in @a j. Generate a failure response. 1082 * 1083 * @param[in,out] ph request that failed 1084 * @param j reply from the server (or NULL) 1085 */ 1086 static void 1087 handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph, 1088 const json_t *j) 1089 { 1090 enum GNUNET_GenericReturnValue res; 1091 1092 { 1093 const char *msg; 1094 const char *desc; 1095 struct GNUNET_JSON_Specification spec[] = { 1096 GNUNET_JSON_spec_string ("error", 1097 &msg), 1098 GNUNET_JSON_spec_string ("error_description", 1099 &desc), 1100 GNUNET_JSON_spec_end () 1101 }; 1102 const char *emsg; 1103 unsigned int line; 1104 1105 res = GNUNET_JSON_parse (j, 1106 spec, 1107 &emsg, 1108 &line); 1109 } 1110 1111 if (GNUNET_OK != res) 1112 { 1113 json_t *body; 1114 1115 GNUNET_break_op (0); 1116 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1117 ph->http_status 1118 = MHD_HTTP_BAD_GATEWAY; 1119 body = GNUNET_JSON_PACK ( 1120 GNUNET_JSON_pack_allow_null ( 1121 GNUNET_JSON_pack_object_incref ("server_response", 1122 (json_t *) j)), 1123 GNUNET_JSON_pack_bool ("debug", 1124 ph->pd->debug_mode), 1125 TALER_JSON_pack_ec ( 1126 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1127 GNUNET_assert (NULL != body); 1128 GNUNET_break ( 1129 GNUNET_SYSERR != 1130 templating_build (ph->connection, 1131 &ph->http_status, 1132 "oauth2-authorization-failure-malformed", 1133 body, 1134 &ph->response)); 1135 json_decref (body); 1136 return; 1137 } 1138 ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED; 1139 ph->http_status = MHD_HTTP_FORBIDDEN; 1140 GNUNET_break ( 1141 GNUNET_SYSERR != 1142 templating_build (ph->connection, 1143 &ph->http_status, 1144 "oauth2-authorization-failure", 1145 j, 1146 &ph->response)); 1147 } 1148 1149 1150 /** 1151 * Type of a callback that receives a JSON @a result. 1152 * 1153 * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *` 1154 * @param status_type how did the process die 1155 * @param code termination status code from the process 1156 * @param attr result some JSON result, NULL if we failed to get an JSON output 1157 */ 1158 static void 1159 converted_proof_cb (void *cls, 1160 enum GNUNET_OS_ProcessStatusType status_type, 1161 unsigned long code, 1162 const json_t *attr) 1163 { 1164 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1165 const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; 1166 1167 ph->ec = NULL; 1168 if ( (NULL == attr) || 1169 (GNUNET_OS_PROCESS_EXITED != status_type) || 1170 (0 != code) ) 1171 { 1172 json_t *body; 1173 char *msg; 1174 1175 GNUNET_break_op (0); 1176 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1177 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1178 if ( (GNUNET_OS_PROCESS_EXITED != status_type) || 1179 (0 != code) ) 1180 GNUNET_asprintf (&msg, 1181 "Attribute converter died with status %d/%ld", 1182 (int) status_type, 1183 code); 1184 else 1185 msg = GNUNET_strdup ( 1186 "Attribute converter response was not in JSON format"); 1187 body = GNUNET_JSON_PACK ( 1188 GNUNET_JSON_pack_string ("converter", 1189 pd->conversion_binary), 1190 GNUNET_JSON_pack_allow_null ( 1191 GNUNET_JSON_pack_object_incref ("attributes", 1192 (json_t *) attr)), 1193 GNUNET_JSON_pack_bool ("debug", 1194 ph->pd->debug_mode), 1195 GNUNET_JSON_pack_string ("message", 1196 msg), 1197 TALER_JSON_pack_ec ( 1198 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1199 GNUNET_free (msg); 1200 GNUNET_break ( 1201 GNUNET_SYSERR != 1202 templating_build (ph->connection, 1203 &ph->http_status, 1204 "oauth2-conversion-failure", 1205 body, 1206 &ph->response)); 1207 json_decref (body); 1208 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1209 ph); 1210 return; 1211 } 1212 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1213 "Attribute conversion output is:\n"); 1214 #if DEBUG 1215 json_dumpf (attr, 1216 stderr, 1217 JSON_INDENT (2)); 1218 fprintf (stderr, 1219 "\n"); 1220 #endif 1221 { 1222 const char *id; 1223 struct GNUNET_JSON_Specification ispec[] = { 1224 GNUNET_JSON_spec_string ("id", 1225 &id), 1226 GNUNET_JSON_spec_end () 1227 }; 1228 enum GNUNET_GenericReturnValue res; 1229 const char *emsg; 1230 unsigned int line; 1231 1232 res = GNUNET_JSON_parse (attr, 1233 ispec, 1234 &emsg, 1235 &line); 1236 if (GNUNET_OK != res) 1237 { 1238 json_t *body; 1239 1240 GNUNET_break_op (0); 1241 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1242 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1243 body = GNUNET_JSON_PACK ( 1244 GNUNET_JSON_pack_string ("converter", 1245 pd->conversion_binary), 1246 GNUNET_JSON_pack_string ("message", 1247 "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"), 1248 GNUNET_JSON_pack_bool ("debug", 1249 ph->pd->debug_mode), 1250 GNUNET_JSON_pack_object_incref ("attributes", 1251 (json_t *) attr), 1252 TALER_JSON_pack_ec ( 1253 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1254 GNUNET_break ( 1255 GNUNET_SYSERR != 1256 templating_build (ph->connection, 1257 &ph->http_status, 1258 "oauth2-conversion-failure", 1259 body, 1260 &ph->response)); 1261 json_decref (body); 1262 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1263 ph); 1264 return; 1265 } 1266 ph->provider_user_id = GNUNET_strdup (id); 1267 } 1268 if (! json_is_string (json_object_get (attr, 1269 "FORM_ID"))) 1270 { 1271 json_t *body; 1272 1273 GNUNET_break_op (0); 1274 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1275 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1276 body = GNUNET_JSON_PACK ( 1277 GNUNET_JSON_pack_string ("converter", 1278 pd->conversion_binary), 1279 GNUNET_JSON_pack_string ("message", 1280 "Missing 'FORM_ID' field in attributes"), 1281 GNUNET_JSON_pack_bool ("debug", 1282 ph->pd->debug_mode), 1283 GNUNET_JSON_pack_object_incref ("attributes", 1284 (json_t *) attr), 1285 TALER_JSON_pack_ec ( 1286 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1287 GNUNET_break ( 1288 GNUNET_SYSERR != 1289 templating_build (ph->connection, 1290 &ph->http_status, 1291 "oauth2-conversion-failure", 1292 body, 1293 &ph->response)); 1294 json_decref (body); 1295 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1296 ph); 1297 return; 1298 } 1299 ph->status = TALER_KYCLOGIC_STATUS_SUCCESS; 1300 ph->response = MHD_create_response_from_buffer_static (0, 1301 ""); 1302 GNUNET_assert (NULL != ph->response); 1303 GNUNET_break (MHD_YES == 1304 MHD_add_response_header ( 1305 ph->response, 1306 MHD_HTTP_HEADER_LOCATION, 1307 ph->pd->post_kyc_redirect_url)); 1308 ph->http_status = MHD_HTTP_SEE_OTHER; 1309 ph->attributes = json_incref ((json_t *) attr); 1310 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1311 ph); 1312 } 1313 1314 1315 /** 1316 * The request for @a ph succeeded (presumably). 1317 * Call continuation with the result. 1318 * 1319 * @param[in,out] ph request that succeeded 1320 * @param j reply from the server 1321 */ 1322 static void 1323 parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, 1324 const json_t *j) 1325 { 1326 const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; 1327 const char *argv[] = { 1328 pd->conversion_binary, 1329 NULL, 1330 }; 1331 1332 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1333 "Calling converter `%s' with JSON\n", 1334 pd->conversion_binary); 1335 #if DEBUG 1336 json_dumpf (j, 1337 stderr, 1338 JSON_INDENT (2)); 1339 #endif 1340 ph->ec = TALER_JSON_external_conversion_start ( 1341 j, 1342 &converted_proof_cb, 1343 ph, 1344 pd->conversion_binary, 1345 argv); 1346 if (NULL != ph->ec) 1347 return; 1348 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1349 "Failed to start OAUTH2 conversion helper `%s'\n", 1350 pd->conversion_binary); 1351 ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; 1352 ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 1353 { 1354 json_t *body; 1355 1356 body = GNUNET_JSON_PACK ( 1357 GNUNET_JSON_pack_string ("converter", 1358 pd->conversion_binary), 1359 GNUNET_JSON_pack_bool ("debug", 1360 ph->pd->debug_mode), 1361 GNUNET_JSON_pack_string ("message", 1362 "Failed to launch KYC conversion helper process."), 1363 TALER_JSON_pack_ec ( 1364 TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)); 1365 GNUNET_break ( 1366 GNUNET_SYSERR != 1367 templating_build (ph->connection, 1368 &ph->http_status, 1369 "oauth2-conversion-failure", 1370 body, 1371 &ph->response)); 1372 json_decref (body); 1373 } 1374 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1375 ph); 1376 } 1377 1378 1379 /** 1380 * After we are done with the CURL interaction we 1381 * need to update our database state with the information 1382 * retrieved. 1383 * 1384 * @param cls our `struct TALER_KYCLOGIC_ProofHandle` 1385 * @param response_code HTTP response code from server, 0 on hard error 1386 * @param response in JSON, NULL if response was not in JSON format 1387 */ 1388 static void 1389 handle_curl_proof_finished (void *cls, 1390 long response_code, 1391 const void *response) 1392 { 1393 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1394 const json_t *j = response; 1395 1396 ph->job = NULL; 1397 switch (response_code) 1398 { 1399 case 0: 1400 { 1401 json_t *body; 1402 1403 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1404 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1405 1406 body = GNUNET_JSON_PACK ( 1407 GNUNET_JSON_pack_string ("message", 1408 "No response from KYC gateway"), 1409 TALER_JSON_pack_ec ( 1410 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1411 GNUNET_break ( 1412 GNUNET_SYSERR != 1413 templating_build (ph->connection, 1414 &ph->http_status, 1415 "oauth2-provider-failure", 1416 body, 1417 &ph->response)); 1418 json_decref (body); 1419 } 1420 break; 1421 case MHD_HTTP_OK: 1422 parse_proof_success_reply (ph, 1423 j); 1424 return; 1425 default: 1426 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1427 "OAuth2.0 info URL returned HTTP status %u\n", 1428 (unsigned int) response_code); 1429 handle_proof_error (ph, 1430 j); 1431 break; 1432 } 1433 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1434 ph); 1435 } 1436 1437 1438 /** 1439 * After we are done with the CURL interaction we 1440 * need to fetch the user's account details. 1441 * 1442 * @param cls our `struct KycProofContext` 1443 * @param response_code HTTP response code from server, 0 on hard error 1444 * @param response in JSON, NULL if response was not in JSON format 1445 */ 1446 static void 1447 handle_curl_login_finished (void *cls, 1448 long response_code, 1449 const void *response) 1450 { 1451 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1452 const json_t *j = response; 1453 1454 ph->job = NULL; 1455 switch (response_code) 1456 { 1457 case MHD_HTTP_OK: 1458 { 1459 const char *access_token; 1460 const char *token_type; 1461 uint64_t expires_in_s; 1462 const char *refresh_token; 1463 bool no_expires; 1464 bool no_refresh; 1465 struct GNUNET_JSON_Specification spec[] = { 1466 GNUNET_JSON_spec_string ("access_token", 1467 &access_token), 1468 GNUNET_JSON_spec_string ("token_type", 1469 &token_type), 1470 GNUNET_JSON_spec_mark_optional ( 1471 GNUNET_JSON_spec_uint64 ("expires_in", 1472 &expires_in_s), 1473 &no_expires), 1474 GNUNET_JSON_spec_mark_optional ( 1475 GNUNET_JSON_spec_string ("refresh_token", 1476 &refresh_token), 1477 &no_refresh), 1478 GNUNET_JSON_spec_end () 1479 }; 1480 CURL *eh; 1481 1482 { 1483 enum GNUNET_GenericReturnValue res; 1484 const char *emsg; 1485 unsigned int line; 1486 1487 res = GNUNET_JSON_parse (j, 1488 spec, 1489 &emsg, 1490 &line); 1491 if (GNUNET_OK != res) 1492 { 1493 json_t *body; 1494 1495 GNUNET_break_op (0); 1496 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1497 ph->http_status 1498 = MHD_HTTP_BAD_GATEWAY; 1499 body = GNUNET_JSON_PACK ( 1500 GNUNET_JSON_pack_object_incref ("server_response", 1501 (json_t *) j), 1502 GNUNET_JSON_pack_bool ("debug", 1503 ph->pd->debug_mode), 1504 GNUNET_JSON_pack_string ("message", 1505 "Unexpected response from KYC gateway: required fields missing or malformed"), 1506 TALER_JSON_pack_ec ( 1507 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1508 GNUNET_break ( 1509 GNUNET_SYSERR != 1510 templating_build (ph->connection, 1511 &ph->http_status, 1512 "oauth2-provider-failure", 1513 body, 1514 &ph->response)); 1515 json_decref (body); 1516 break; 1517 } 1518 } 1519 if (0 != strcasecmp (token_type, 1520 "bearer")) 1521 { 1522 json_t *body; 1523 1524 GNUNET_break_op (0); 1525 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1526 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1527 body = GNUNET_JSON_PACK ( 1528 GNUNET_JSON_pack_object_incref ("server_response", 1529 (json_t *) j), 1530 GNUNET_JSON_pack_bool ("debug", 1531 ph->pd->debug_mode), 1532 GNUNET_JSON_pack_string ("message", 1533 "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"), 1534 TALER_JSON_pack_ec ( 1535 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1536 GNUNET_break ( 1537 GNUNET_SYSERR != 1538 templating_build (ph->connection, 1539 &ph->http_status, 1540 "oauth2-provider-failure", 1541 body, 1542 &ph->response)); 1543 json_decref (body); 1544 break; 1545 } 1546 1547 /* We guard against a few characters that could 1548 conceivably be abused to mess with the HTTP header */ 1549 if ( (NULL != strchr (access_token, 1550 '\n')) || 1551 (NULL != strchr (access_token, 1552 '\r')) || 1553 (NULL != strchr (access_token, 1554 ' ')) || 1555 (NULL != strchr (access_token, 1556 ';')) ) 1557 { 1558 json_t *body; 1559 1560 GNUNET_break_op (0); 1561 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1562 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1563 body = GNUNET_JSON_PACK ( 1564 GNUNET_JSON_pack_object_incref ("server_response", 1565 (json_t *) j), 1566 GNUNET_JSON_pack_bool ("debug", 1567 ph->pd->debug_mode), 1568 GNUNET_JSON_pack_string ("message", 1569 "Illegal character in access token"), 1570 TALER_JSON_pack_ec ( 1571 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1572 GNUNET_break ( 1573 GNUNET_SYSERR != 1574 templating_build (ph->connection, 1575 &ph->http_status, 1576 "oauth2-provider-failure", 1577 body, 1578 &ph->response)); 1579 json_decref (body); 1580 break; 1581 } 1582 1583 eh = curl_easy_init (); 1584 GNUNET_assert (NULL != eh); 1585 GNUNET_assert (CURLE_OK == 1586 curl_easy_setopt (eh, 1587 CURLOPT_URL, 1588 ph->pd->info_url)); 1589 { 1590 char *hdr; 1591 struct curl_slist *slist; 1592 1593 GNUNET_asprintf (&hdr, 1594 "%s: Bearer %s", 1595 MHD_HTTP_HEADER_AUTHORIZATION, 1596 access_token); 1597 slist = curl_slist_append (NULL, 1598 hdr); 1599 ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx, 1600 eh, 1601 slist, 1602 &handle_curl_proof_finished, 1603 ph); 1604 curl_slist_free_all (slist); 1605 GNUNET_free (hdr); 1606 } 1607 return; 1608 } 1609 default: 1610 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1611 "OAuth2.0 login URL returned HTTP status %u\n", 1612 (unsigned int) response_code); 1613 handle_proof_error (ph, 1614 j); 1615 break; 1616 } 1617 return_proof_response (ph); 1618 } 1619 1620 1621 /** 1622 * Check KYC status and return status to human. 1623 * 1624 * @param cls the @e cls of this struct with the plugin-specific state 1625 * @param pd provider configuration details 1626 * @param connection MHD connection object (for HTTP headers) 1627 * @param account_id which account to trigger process for 1628 * @param process_row row in the legitimization processes table the legitimization is for 1629 * @param provider_user_id user ID (or NULL) the proof is for 1630 * @param provider_legitimization_id legitimization ID the proof is for 1631 * @param cb function to call with the result 1632 * @param cb_cls closure for @a cb 1633 * @return handle to cancel operation early 1634 */ 1635 static struct TALER_KYCLOGIC_ProofHandle * 1636 oauth2_proof (void *cls, 1637 const struct TALER_KYCLOGIC_ProviderDetails *pd, 1638 struct MHD_Connection *connection, 1639 const struct TALER_NormalizedPaytoHashP *account_id, 1640 uint64_t process_row, 1641 const char *provider_user_id, 1642 const char *provider_legitimization_id, 1643 TALER_KYCLOGIC_ProofCallback cb, 1644 void *cb_cls) 1645 { 1646 struct PluginState *ps = cls; 1647 struct TALER_KYCLOGIC_ProofHandle *ph; 1648 const char *code; 1649 1650 GNUNET_break (NULL == provider_user_id); 1651 ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle); 1652 GNUNET_snprintf (ph->provider_legitimization_id, 1653 sizeof (ph->provider_legitimization_id), 1654 "%llu", 1655 (unsigned long long) process_row); 1656 if ( (NULL != provider_legitimization_id) && 1657 (0 != strcmp (provider_legitimization_id, 1658 ph->provider_legitimization_id))) 1659 { 1660 GNUNET_break (0); 1661 GNUNET_free (ph); 1662 return NULL; 1663 } 1664 1665 ph->pd = pd; 1666 ph->connection = connection; 1667 ph->h_payto = *account_id; 1668 ph->cb = cb; 1669 ph->cb_cls = cb_cls; 1670 code = MHD_lookup_connection_value (connection, 1671 MHD_GET_ARGUMENT_KIND, 1672 "code"); 1673 if (NULL == code) 1674 { 1675 const char *err; 1676 const char *desc; 1677 const char *euri; 1678 json_t *body; 1679 1680 err = MHD_lookup_connection_value (connection, 1681 MHD_GET_ARGUMENT_KIND, 1682 "error"); 1683 if (NULL == err) 1684 { 1685 GNUNET_break_op (0); 1686 ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING; 1687 ph->http_status = MHD_HTTP_BAD_REQUEST; 1688 body = GNUNET_JSON_PACK ( 1689 GNUNET_JSON_pack_string ("message", 1690 "'code' parameter malformed"), 1691 TALER_JSON_pack_ec ( 1692 TALER_EC_GENERIC_PARAMETER_MALFORMED)); 1693 GNUNET_break ( 1694 GNUNET_SYSERR != 1695 templating_build (ph->connection, 1696 &ph->http_status, 1697 "oauth2-bad-request", 1698 body, 1699 &ph->response)); 1700 json_decref (body); 1701 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1702 ph); 1703 return ph; 1704 } 1705 desc = MHD_lookup_connection_value (connection, 1706 MHD_GET_ARGUMENT_KIND, 1707 "error_description"); 1708 euri = MHD_lookup_connection_value (connection, 1709 MHD_GET_ARGUMENT_KIND, 1710 "error_uri"); 1711 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1712 "OAuth2 process %llu failed with error `%s'\n", 1713 (unsigned long long) process_row, 1714 err); 1715 if (0 == strcasecmp (err, 1716 "server_error")) 1717 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1718 else if (0 == strcasecmp (err, 1719 "unauthorized_client")) 1720 ph->status = TALER_KYCLOGIC_STATUS_FAILED; 1721 else if (0 == strcasecmp (err, 1722 "temporarily_unavailable")) 1723 ph->status = TALER_KYCLOGIC_STATUS_PENDING; 1724 else 1725 ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; 1726 ph->http_status = MHD_HTTP_FORBIDDEN; 1727 body = GNUNET_JSON_PACK ( 1728 GNUNET_JSON_pack_string ("error", 1729 err), 1730 GNUNET_JSON_pack_allow_null ( 1731 GNUNET_JSON_pack_string ("error_details", 1732 desc)), 1733 GNUNET_JSON_pack_allow_null ( 1734 GNUNET_JSON_pack_string ("error_uri", 1735 euri))); 1736 GNUNET_break ( 1737 GNUNET_SYSERR != 1738 templating_build (ph->connection, 1739 &ph->http_status, 1740 "oauth2-authentication-failure", 1741 body, 1742 &ph->response)); 1743 json_decref (body); 1744 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1745 ph); 1746 return ph; 1747 1748 } 1749 1750 ph->eh = curl_easy_init (); 1751 GNUNET_assert (NULL != ph->eh); 1752 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1753 "Requesting OAuth 2.0 data via HTTP POST `%s'\n", 1754 pd->token_url); 1755 GNUNET_assert (CURLE_OK == 1756 curl_easy_setopt (ph->eh, 1757 CURLOPT_URL, 1758 pd->token_url)); 1759 #if DEBUG 1760 GNUNET_assert (CURLE_OK == 1761 curl_easy_setopt (ph->eh, 1762 CURLOPT_VERBOSE, 1763 1)); 1764 #endif 1765 GNUNET_assert (CURLE_OK == 1766 curl_easy_setopt (ph->eh, 1767 CURLOPT_POST, 1768 1)); 1769 { 1770 char *client_id; 1771 char *client_secret; 1772 char *authorization_code; 1773 char *redirect_uri_encoded; 1774 char *hps; 1775 1776 hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto, 1777 sizeof (ph->h_payto)); 1778 { 1779 char *redirect_uri; 1780 1781 GNUNET_asprintf (&redirect_uri, 1782 "%skyc-proof/%s", 1783 ps->exchange_base_url, 1784 &pd->section[strlen ("kyc-provider-")]); 1785 redirect_uri_encoded = TALER_urlencode (redirect_uri); 1786 GNUNET_free (redirect_uri); 1787 } 1788 GNUNET_assert (NULL != redirect_uri_encoded); 1789 client_id = curl_easy_escape (ph->eh, 1790 pd->client_id, 1791 0); 1792 GNUNET_assert (NULL != client_id); 1793 client_secret = curl_easy_escape (ph->eh, 1794 pd->client_secret, 1795 0); 1796 GNUNET_assert (NULL != client_secret); 1797 authorization_code = curl_easy_escape (ph->eh, 1798 code, 1799 0); 1800 GNUNET_assert (NULL != authorization_code); 1801 GNUNET_asprintf (&ph->post_body, 1802 "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code", 1803 client_id, 1804 redirect_uri_encoded, 1805 hps, 1806 client_secret, 1807 authorization_code); 1808 curl_free (authorization_code); 1809 curl_free (client_secret); 1810 GNUNET_free (redirect_uri_encoded); 1811 GNUNET_free (hps); 1812 curl_free (client_id); 1813 } 1814 GNUNET_assert (CURLE_OK == 1815 curl_easy_setopt (ph->eh, 1816 CURLOPT_POSTFIELDS, 1817 ph->post_body)); 1818 GNUNET_assert (CURLE_OK == 1819 curl_easy_setopt (ph->eh, 1820 CURLOPT_FOLLOWLOCATION, 1821 1L)); 1822 /* limit MAXREDIRS to 5 as a simple security measure against 1823 a potential infinite loop caused by a malicious target */ 1824 GNUNET_assert (CURLE_OK == 1825 curl_easy_setopt (ph->eh, 1826 CURLOPT_MAXREDIRS, 1827 5L)); 1828 1829 ph->job = GNUNET_CURL_job_add (ps->curl_ctx, 1830 ph->eh, 1831 &handle_curl_login_finished, 1832 ph); 1833 return ph; 1834 } 1835 1836 1837 /** 1838 * Function to asynchronously return the 404 not found 1839 * page for the webhook. 1840 * 1841 * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *` 1842 */ 1843 static void 1844 wh_return_not_found (void *cls) 1845 { 1846 struct TALER_KYCLOGIC_WebhookHandle *wh = cls; 1847 struct MHD_Response *response; 1848 1849 wh->task = NULL; 1850 response = MHD_create_response_from_buffer_static (0, 1851 ""); 1852 wh->cb (wh->cb_cls, 1853 0LLU, 1854 NULL, 1855 false, 1856 NULL, 1857 NULL, 1858 NULL, 1859 TALER_KYCLOGIC_STATUS_KEEP, 1860 GNUNET_TIME_UNIT_ZERO_ABS, 1861 NULL, 1862 MHD_HTTP_NOT_FOUND, 1863 response); 1864 GNUNET_free (wh); 1865 } 1866 1867 1868 /** 1869 * Check KYC status and return result for Webhook. 1870 * 1871 * @param cls the @e cls of this struct with the plugin-specific state 1872 * @param pd provider configuration details 1873 * @param plc callback to lookup accounts with 1874 * @param plc_cls closure for @a plc 1875 * @param http_method HTTP method used for the webhook 1876 * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array 1877 * @param connection MHD connection object (for HTTP headers) 1878 * @param body HTTP request body, or NULL if not available 1879 * @param cb function to call with the result 1880 * @param cb_cls closure for @a cb 1881 * @return handle to cancel operation early 1882 */ 1883 static struct TALER_KYCLOGIC_WebhookHandle * 1884 oauth2_webhook (void *cls, 1885 const struct TALER_KYCLOGIC_ProviderDetails *pd, 1886 TALER_KYCLOGIC_ProviderLookupCallback plc, 1887 void *plc_cls, 1888 const char *http_method, 1889 const char *const url_path[], 1890 struct MHD_Connection *connection, 1891 const json_t *body, 1892 TALER_KYCLOGIC_WebhookCallback cb, 1893 void *cb_cls) 1894 { 1895 struct PluginState *ps = cls; 1896 struct TALER_KYCLOGIC_WebhookHandle *wh; 1897 1898 (void) pd; 1899 (void) plc; 1900 (void) plc_cls; 1901 (void) http_method; 1902 (void) url_path; 1903 (void) connection; 1904 (void) body; 1905 GNUNET_break_op (0); 1906 wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle); 1907 wh->cb = cb; 1908 wh->cb_cls = cb_cls; 1909 wh->ps = ps; 1910 wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found, 1911 wh); 1912 return wh; 1913 } 1914 1915 1916 /** 1917 * Cancel KYC webhook execution. 1918 * 1919 * @param[in] wh handle of operation to cancel 1920 */ 1921 static void 1922 oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh) 1923 { 1924 GNUNET_SCHEDULER_cancel (wh->task); 1925 GNUNET_free (wh); 1926 } 1927 1928 1929 /** 1930 * Initialize OAuth2.0 KYC logic plugin 1931 * 1932 * @param cls a configuration instance 1933 * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin` 1934 */ 1935 void * 1936 libtaler_plugin_kyclogic_oauth2_init (void *cls); 1937 1938 /* declaration to avoid compiler warning */ 1939 void * 1940 libtaler_plugin_kyclogic_oauth2_init (void *cls) 1941 { 1942 const struct GNUNET_CONFIGURATION_Handle *cfg = cls; 1943 struct TALER_KYCLOGIC_Plugin *plugin; 1944 struct PluginState *ps; 1945 1946 ps = GNUNET_new (struct PluginState); 1947 ps->cfg = cfg; 1948 if (GNUNET_OK != 1949 GNUNET_CONFIGURATION_get_value_string (cfg, 1950 "exchange", 1951 "BASE_URL", 1952 &ps->exchange_base_url)) 1953 { 1954 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1955 "exchange", 1956 "BASE_URL"); 1957 GNUNET_free (ps); 1958 return NULL; 1959 } 1960 ps->curl_ctx 1961 = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1962 &ps->curl_rc); 1963 if (NULL == ps->curl_ctx) 1964 { 1965 GNUNET_break (0); 1966 GNUNET_free (ps->exchange_base_url); 1967 GNUNET_free (ps); 1968 return NULL; 1969 } 1970 ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx); 1971 1972 plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin); 1973 plugin->cls = ps; 1974 plugin->load_configuration 1975 = &oauth2_load_configuration; 1976 plugin->unload_configuration 1977 = &oauth2_unload_configuration; 1978 plugin->initiate 1979 = &oauth2_initiate; 1980 plugin->initiate_get_expiration 1981 = &oauth2_initiate_get_expiration; 1982 plugin->initiate_cancel 1983 = &oauth2_initiate_cancel; 1984 plugin->proof 1985 = &oauth2_proof; 1986 plugin->proof_cancel 1987 = &oauth2_proof_cancel; 1988 plugin->webhook 1989 = &oauth2_webhook; 1990 plugin->webhook_cancel 1991 = &oauth2_webhook_cancel; 1992 return plugin; 1993 } 1994 1995 1996 /** 1997 * Unload authorization plugin 1998 * 1999 * @param cls a `struct TALER_KYCLOGIC_Plugin` 2000 * @return NULL (always) 2001 */ 2002 void * 2003 libtaler_plugin_kyclogic_oauth2_done (void *cls); 2004 2005 /* declaration to avoid compiler warning */ 2006 void * 2007 libtaler_plugin_kyclogic_oauth2_done (void *cls) 2008 { 2009 struct TALER_KYCLOGIC_Plugin *plugin = cls; 2010 struct PluginState *ps = plugin->cls; 2011 2012 if (NULL != ps->curl_ctx) 2013 { 2014 GNUNET_CURL_fini (ps->curl_ctx); 2015 ps->curl_ctx = NULL; 2016 } 2017 if (NULL != ps->curl_rc) 2018 { 2019 GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc); 2020 ps->curl_rc = NULL; 2021 } 2022 GNUNET_free (ps->exchange_base_url); 2023 GNUNET_free (ps); 2024 GNUNET_free (plugin); 2025 return NULL; 2026 }