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