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