plugin_kyclogic_oauth2.c (58499B)
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 * Load a @a template and substitute using @a root, returning the result in a 992 * @a reply encoded suitable for the @a connection with the given @a 993 * http_status code. On errors, the @a http_status code 994 * is updated to reflect the type of error encoded in the 995 * @a reply. 996 * 997 * @param connection the connection we act upon 998 * @param[in,out] http_status code to use on success, 999 * set to alternative code on failure 1000 * @param template basename of the template to load 1001 * @param root JSON object to pass as the root context 1002 * @param[out] reply where to write the response object 1003 * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued, 1004 * #GNUNET_SYSERR on failure (to queue an error) 1005 */ 1006 static enum GNUNET_GenericReturnValue 1007 templating_build (struct MHD_Connection *connection, 1008 unsigned int *http_status, 1009 const char *template, 1010 const json_t *root, 1011 struct MHD_Response **reply) 1012 { 1013 enum GNUNET_GenericReturnValue ret; 1014 1015 ret = TALER_TEMPLATING_build (connection, 1016 http_status, 1017 template, 1018 NULL, 1019 NULL, 1020 root, 1021 reply); 1022 if (GNUNET_SYSERR != ret) 1023 { 1024 GNUNET_break (MHD_NO != 1025 MHD_add_response_header (*reply, 1026 MHD_HTTP_HEADER_CONTENT_TYPE, 1027 "text/html")); 1028 } 1029 return ret; 1030 } 1031 1032 1033 /** 1034 * The request for @a ph failed. We may have gotten a useful error 1035 * message in @a j. Generate a failure response. 1036 * 1037 * @param[in,out] ph request that failed 1038 * @param j reply from the server (or NULL) 1039 */ 1040 static void 1041 handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph, 1042 const json_t *j) 1043 { 1044 enum GNUNET_GenericReturnValue res; 1045 1046 { 1047 const char *msg; 1048 const char *desc; 1049 struct GNUNET_JSON_Specification spec[] = { 1050 GNUNET_JSON_spec_string ("error", 1051 &msg), 1052 GNUNET_JSON_spec_string ("error_description", 1053 &desc), 1054 GNUNET_JSON_spec_end () 1055 }; 1056 const char *emsg; 1057 unsigned int line; 1058 1059 res = GNUNET_JSON_parse (j, 1060 spec, 1061 &emsg, 1062 &line); 1063 } 1064 1065 if (GNUNET_OK != res) 1066 { 1067 json_t *body; 1068 1069 GNUNET_break_op (0); 1070 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1071 ph->http_status 1072 = MHD_HTTP_BAD_GATEWAY; 1073 body = GNUNET_JSON_PACK ( 1074 GNUNET_JSON_pack_allow_null ( 1075 GNUNET_JSON_pack_object_incref ("server_response", 1076 (json_t *) j)), 1077 GNUNET_JSON_pack_bool ("debug", 1078 ph->pd->debug_mode), 1079 TALER_JSON_pack_ec ( 1080 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1081 GNUNET_assert (NULL != body); 1082 GNUNET_break ( 1083 GNUNET_SYSERR != 1084 templating_build (ph->connection, 1085 &ph->http_status, 1086 "oauth2-authorization-failure-malformed", 1087 body, 1088 &ph->response)); 1089 json_decref (body); 1090 return; 1091 } 1092 ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED; 1093 ph->http_status = MHD_HTTP_FORBIDDEN; 1094 GNUNET_break ( 1095 GNUNET_SYSERR != 1096 templating_build (ph->connection, 1097 &ph->http_status, 1098 "oauth2-authorization-failure", 1099 j, 1100 &ph->response)); 1101 } 1102 1103 1104 /** 1105 * Type of a callback that receives a JSON @a result. 1106 * 1107 * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *` 1108 * @param status_type how did the process die 1109 * @param code termination status code from the process 1110 * @param attr result some JSON result, NULL if we failed to get an JSON output 1111 */ 1112 static void 1113 converted_proof_cb (void *cls, 1114 enum GNUNET_OS_ProcessStatusType status_type, 1115 unsigned long code, 1116 const json_t *attr) 1117 { 1118 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1119 const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; 1120 1121 ph->ec = NULL; 1122 if ( (NULL == attr) || 1123 (0 != code) ) 1124 { 1125 json_t *body; 1126 char *msg; 1127 1128 GNUNET_break_op (0); 1129 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1130 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1131 if (0 != code) 1132 GNUNET_asprintf (&msg, 1133 "Attribute converter exited with status %ld", 1134 code); 1135 else 1136 msg = GNUNET_strdup ( 1137 "Attribute converter response was not in JSON format"); 1138 body = GNUNET_JSON_PACK ( 1139 GNUNET_JSON_pack_string ("converter", 1140 pd->conversion_binary), 1141 GNUNET_JSON_pack_allow_null ( 1142 GNUNET_JSON_pack_object_incref ("attributes", 1143 (json_t *) attr)), 1144 GNUNET_JSON_pack_bool ("debug", 1145 ph->pd->debug_mode), 1146 GNUNET_JSON_pack_string ("message", 1147 msg), 1148 TALER_JSON_pack_ec ( 1149 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1150 GNUNET_free (msg); 1151 GNUNET_break ( 1152 GNUNET_SYSERR != 1153 templating_build (ph->connection, 1154 &ph->http_status, 1155 "oauth2-conversion-failure", 1156 body, 1157 &ph->response)); 1158 json_decref (body); 1159 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1160 ph); 1161 return; 1162 } 1163 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1164 "Attribute conversion output is:\n"); 1165 #if DEBUG 1166 json_dumpf (attr, 1167 stderr, 1168 JSON_INDENT (2)); 1169 fprintf (stderr, 1170 "\n"); 1171 #endif 1172 { 1173 const char *id; 1174 struct GNUNET_JSON_Specification ispec[] = { 1175 GNUNET_JSON_spec_string ("id", 1176 &id), 1177 GNUNET_JSON_spec_end () 1178 }; 1179 enum GNUNET_GenericReturnValue res; 1180 const char *emsg; 1181 unsigned int line; 1182 1183 res = GNUNET_JSON_parse (attr, 1184 ispec, 1185 &emsg, 1186 &line); 1187 if (GNUNET_OK != res) 1188 { 1189 json_t *body; 1190 1191 GNUNET_break_op (0); 1192 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1193 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1194 body = GNUNET_JSON_PACK ( 1195 GNUNET_JSON_pack_string ("converter", 1196 pd->conversion_binary), 1197 GNUNET_JSON_pack_string ("message", 1198 "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"), 1199 GNUNET_JSON_pack_bool ("debug", 1200 ph->pd->debug_mode), 1201 GNUNET_JSON_pack_object_incref ("attributes", 1202 (json_t *) attr), 1203 TALER_JSON_pack_ec ( 1204 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1205 GNUNET_break ( 1206 GNUNET_SYSERR != 1207 templating_build (ph->connection, 1208 &ph->http_status, 1209 "oauth2-conversion-failure", 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->provider_user_id = GNUNET_strdup (id); 1218 } 1219 if (! json_is_string (json_object_get (attr, 1220 "FORM_ID"))) 1221 { 1222 json_t *body; 1223 1224 GNUNET_break_op (0); 1225 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1226 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1227 body = GNUNET_JSON_PACK ( 1228 GNUNET_JSON_pack_string ("converter", 1229 pd->conversion_binary), 1230 GNUNET_JSON_pack_string ("message", 1231 "Missing 'FORM_ID' field in attributes"), 1232 GNUNET_JSON_pack_bool ("debug", 1233 ph->pd->debug_mode), 1234 GNUNET_JSON_pack_object_incref ("attributes", 1235 (json_t *) attr), 1236 TALER_JSON_pack_ec ( 1237 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1238 GNUNET_break ( 1239 GNUNET_SYSERR != 1240 templating_build (ph->connection, 1241 &ph->http_status, 1242 "oauth2-conversion-failure", 1243 body, 1244 &ph->response)); 1245 json_decref (body); 1246 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1247 ph); 1248 return; 1249 } 1250 ph->status = TALER_KYCLOGIC_STATUS_SUCCESS; 1251 ph->response = MHD_create_response_from_buffer_static (0, 1252 ""); 1253 GNUNET_assert (NULL != ph->response); 1254 GNUNET_break (MHD_YES == 1255 MHD_add_response_header ( 1256 ph->response, 1257 MHD_HTTP_HEADER_LOCATION, 1258 ph->pd->post_kyc_redirect_url)); 1259 ph->http_status = MHD_HTTP_SEE_OTHER; 1260 ph->attributes = json_incref ((json_t *) attr); 1261 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1262 ph); 1263 } 1264 1265 1266 /** 1267 * The request for @a ph succeeded (presumably). 1268 * Call continuation with the result. 1269 * 1270 * @param[in,out] ph request that succeeded 1271 * @param j reply from the server 1272 */ 1273 static void 1274 parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, 1275 const json_t *j) 1276 { 1277 const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; 1278 const char *argv[] = { 1279 pd->conversion_binary, 1280 NULL, 1281 }; 1282 1283 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1284 "Calling converter `%s' with JSON\n", 1285 pd->conversion_binary); 1286 #if DEBUG 1287 json_dumpf (j, 1288 stderr, 1289 JSON_INDENT (2)); 1290 #endif 1291 ph->ec = TALER_JSON_external_conversion_start ( 1292 j, 1293 &converted_proof_cb, 1294 ph, 1295 pd->conversion_binary, 1296 argv); 1297 if (NULL != ph->ec) 1298 return; 1299 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1300 "Failed to start OAUTH2 conversion helper `%s'\n", 1301 pd->conversion_binary); 1302 ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; 1303 ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 1304 { 1305 json_t *body; 1306 1307 body = GNUNET_JSON_PACK ( 1308 GNUNET_JSON_pack_string ("converter", 1309 pd->conversion_binary), 1310 GNUNET_JSON_pack_bool ("debug", 1311 ph->pd->debug_mode), 1312 GNUNET_JSON_pack_string ("message", 1313 "Failed to launch KYC conversion helper process."), 1314 TALER_JSON_pack_ec ( 1315 TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)); 1316 GNUNET_break ( 1317 GNUNET_SYSERR != 1318 templating_build (ph->connection, 1319 &ph->http_status, 1320 "oauth2-conversion-failure", 1321 body, 1322 &ph->response)); 1323 json_decref (body); 1324 } 1325 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1326 ph); 1327 } 1328 1329 1330 /** 1331 * After we are done with the CURL interaction we 1332 * need to update our database state with the information 1333 * retrieved. 1334 * 1335 * @param cls our `struct TALER_KYCLOGIC_ProofHandle` 1336 * @param response_code HTTP response code from server, 0 on hard error 1337 * @param response in JSON, NULL if response was not in JSON format 1338 */ 1339 static void 1340 handle_curl_proof_finished (void *cls, 1341 long response_code, 1342 const void *response) 1343 { 1344 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1345 const json_t *j = response; 1346 1347 ph->job = NULL; 1348 switch (response_code) 1349 { 1350 case 0: 1351 { 1352 json_t *body; 1353 1354 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1355 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1356 1357 body = GNUNET_JSON_PACK ( 1358 GNUNET_JSON_pack_string ("message", 1359 "No response from KYC gateway"), 1360 TALER_JSON_pack_ec ( 1361 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1362 GNUNET_break ( 1363 GNUNET_SYSERR != 1364 templating_build (ph->connection, 1365 &ph->http_status, 1366 "oauth2-provider-failure", 1367 body, 1368 &ph->response)); 1369 json_decref (body); 1370 } 1371 break; 1372 case MHD_HTTP_OK: 1373 parse_proof_success_reply (ph, 1374 j); 1375 return; 1376 default: 1377 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1378 "OAuth2.0 info URL returned HTTP status %u\n", 1379 (unsigned int) response_code); 1380 handle_proof_error (ph, 1381 j); 1382 break; 1383 } 1384 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1385 ph); 1386 } 1387 1388 1389 /** 1390 * After we are done with the CURL interaction we 1391 * need to fetch the user's account details. 1392 * 1393 * @param cls our `struct KycProofContext` 1394 * @param response_code HTTP response code from server, 0 on hard error 1395 * @param response in JSON, NULL if response was not in JSON format 1396 */ 1397 static void 1398 handle_curl_login_finished (void *cls, 1399 long response_code, 1400 const void *response) 1401 { 1402 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1403 const json_t *j = response; 1404 1405 ph->job = NULL; 1406 switch (response_code) 1407 { 1408 case MHD_HTTP_OK: 1409 { 1410 const char *access_token; 1411 const char *token_type; 1412 uint64_t expires_in_s; 1413 const char *refresh_token; 1414 bool no_expires; 1415 bool no_refresh; 1416 struct GNUNET_JSON_Specification spec[] = { 1417 GNUNET_JSON_spec_string ("access_token", 1418 &access_token), 1419 GNUNET_JSON_spec_string ("token_type", 1420 &token_type), 1421 GNUNET_JSON_spec_mark_optional ( 1422 GNUNET_JSON_spec_uint64 ("expires_in", 1423 &expires_in_s), 1424 &no_expires), 1425 GNUNET_JSON_spec_mark_optional ( 1426 GNUNET_JSON_spec_string ("refresh_token", 1427 &refresh_token), 1428 &no_refresh), 1429 GNUNET_JSON_spec_end () 1430 }; 1431 CURL *eh; 1432 1433 { 1434 enum GNUNET_GenericReturnValue res; 1435 const char *emsg; 1436 unsigned int line; 1437 1438 res = GNUNET_JSON_parse (j, 1439 spec, 1440 &emsg, 1441 &line); 1442 if (GNUNET_OK != res) 1443 { 1444 json_t *body; 1445 1446 GNUNET_break_op (0); 1447 ph->http_status 1448 = 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 response from KYC gateway: required fields missing or malformed"), 1456 TALER_JSON_pack_ec ( 1457 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1458 GNUNET_break ( 1459 GNUNET_SYSERR != 1460 templating_build (ph->connection, 1461 &ph->http_status, 1462 "oauth2-provider-failure", 1463 body, 1464 &ph->response)); 1465 json_decref (body); 1466 break; 1467 } 1468 } 1469 if (0 != strcasecmp (token_type, 1470 "bearer")) 1471 { 1472 json_t *body; 1473 1474 GNUNET_break_op (0); 1475 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1476 body = GNUNET_JSON_PACK ( 1477 GNUNET_JSON_pack_object_incref ("server_response", 1478 (json_t *) j), 1479 GNUNET_JSON_pack_bool ("debug", 1480 ph->pd->debug_mode), 1481 GNUNET_JSON_pack_string ("message", 1482 "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"), 1483 TALER_JSON_pack_ec ( 1484 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1485 GNUNET_break ( 1486 GNUNET_SYSERR != 1487 templating_build (ph->connection, 1488 &ph->http_status, 1489 "oauth2-provider-failure", 1490 body, 1491 &ph->response)); 1492 json_decref (body); 1493 break; 1494 } 1495 1496 /* We guard against a few characters that could 1497 conceivably be abused to mess with the HTTP header */ 1498 if ( (NULL != strchr (access_token, 1499 '\n')) || 1500 (NULL != strchr (access_token, 1501 '\r')) || 1502 (NULL != strchr (access_token, 1503 ' ')) || 1504 (NULL != strchr (access_token, 1505 ';')) ) 1506 { 1507 json_t *body; 1508 1509 GNUNET_break_op (0); 1510 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1511 body = GNUNET_JSON_PACK ( 1512 GNUNET_JSON_pack_object_incref ("server_response", 1513 (json_t *) j), 1514 GNUNET_JSON_pack_bool ("debug", 1515 ph->pd->debug_mode), 1516 GNUNET_JSON_pack_string ("message", 1517 "Illegal character in access token"), 1518 TALER_JSON_pack_ec ( 1519 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1520 GNUNET_break ( 1521 GNUNET_SYSERR != 1522 templating_build (ph->connection, 1523 &ph->http_status, 1524 "oauth2-provider-failure", 1525 body, 1526 &ph->response)); 1527 json_decref (body); 1528 break; 1529 } 1530 1531 eh = curl_easy_init (); 1532 GNUNET_assert (NULL != eh); 1533 GNUNET_assert (CURLE_OK == 1534 curl_easy_setopt (eh, 1535 CURLOPT_URL, 1536 ph->pd->info_url)); 1537 { 1538 char *hdr; 1539 struct curl_slist *slist; 1540 1541 GNUNET_asprintf (&hdr, 1542 "%s: Bearer %s", 1543 MHD_HTTP_HEADER_AUTHORIZATION, 1544 access_token); 1545 slist = curl_slist_append (NULL, 1546 hdr); 1547 ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx, 1548 eh, 1549 slist, 1550 &handle_curl_proof_finished, 1551 ph); 1552 curl_slist_free_all (slist); 1553 GNUNET_free (hdr); 1554 } 1555 return; 1556 } 1557 default: 1558 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1559 "OAuth2.0 login URL returned HTTP status %u\n", 1560 (unsigned int) response_code); 1561 handle_proof_error (ph, 1562 j); 1563 break; 1564 } 1565 return_proof_response (ph); 1566 } 1567 1568 1569 /** 1570 * Check KYC status and return status to human. 1571 * 1572 * @param cls the @e cls of this struct with the plugin-specific state 1573 * @param pd provider configuration details 1574 * @param connection MHD connection object (for HTTP headers) 1575 * @param account_id which account to trigger process for 1576 * @param process_row row in the legitimization processes table the legitimization is for 1577 * @param provider_user_id user ID (or NULL) the proof is for 1578 * @param provider_legitimization_id legitimization ID the proof is for 1579 * @param cb function to call with the result 1580 * @param cb_cls closure for @a cb 1581 * @return handle to cancel operation early 1582 */ 1583 static struct TALER_KYCLOGIC_ProofHandle * 1584 oauth2_proof (void *cls, 1585 const struct TALER_KYCLOGIC_ProviderDetails *pd, 1586 struct MHD_Connection *connection, 1587 const struct TALER_NormalizedPaytoHashP *account_id, 1588 uint64_t process_row, 1589 const char *provider_user_id, 1590 const char *provider_legitimization_id, 1591 TALER_KYCLOGIC_ProofCallback cb, 1592 void *cb_cls) 1593 { 1594 struct PluginState *ps = cls; 1595 struct TALER_KYCLOGIC_ProofHandle *ph; 1596 const char *code; 1597 1598 GNUNET_break (NULL == provider_user_id); 1599 ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle); 1600 GNUNET_snprintf (ph->provider_legitimization_id, 1601 sizeof (ph->provider_legitimization_id), 1602 "%llu", 1603 (unsigned long long) process_row); 1604 if ( (NULL != provider_legitimization_id) && 1605 (0 != strcmp (provider_legitimization_id, 1606 ph->provider_legitimization_id))) 1607 { 1608 GNUNET_break (0); 1609 GNUNET_free (ph); 1610 return NULL; 1611 } 1612 1613 ph->pd = pd; 1614 ph->connection = connection; 1615 ph->h_payto = *account_id; 1616 ph->cb = cb; 1617 ph->cb_cls = cb_cls; 1618 code = MHD_lookup_connection_value (connection, 1619 MHD_GET_ARGUMENT_KIND, 1620 "code"); 1621 if (NULL == code) 1622 { 1623 const char *err; 1624 const char *desc; 1625 const char *euri; 1626 json_t *body; 1627 1628 err = MHD_lookup_connection_value (connection, 1629 MHD_GET_ARGUMENT_KIND, 1630 "error"); 1631 if (NULL == err) 1632 { 1633 GNUNET_break_op (0); 1634 ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING; 1635 ph->http_status = MHD_HTTP_BAD_REQUEST; 1636 body = GNUNET_JSON_PACK ( 1637 GNUNET_JSON_pack_string ("message", 1638 "'code' parameter malformed"), 1639 TALER_JSON_pack_ec ( 1640 TALER_EC_GENERIC_PARAMETER_MALFORMED)); 1641 GNUNET_break ( 1642 GNUNET_SYSERR != 1643 templating_build (ph->connection, 1644 &ph->http_status, 1645 "oauth2-bad-request", 1646 body, 1647 &ph->response)); 1648 json_decref (body); 1649 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1650 ph); 1651 return ph; 1652 } 1653 desc = MHD_lookup_connection_value (connection, 1654 MHD_GET_ARGUMENT_KIND, 1655 "error_description"); 1656 euri = MHD_lookup_connection_value (connection, 1657 MHD_GET_ARGUMENT_KIND, 1658 "error_uri"); 1659 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1660 "OAuth2 process %llu failed with error `%s'\n", 1661 (unsigned long long) process_row, 1662 err); 1663 if (0 == strcasecmp (err, 1664 "server_error")) 1665 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1666 else if (0 == strcasecmp (err, 1667 "unauthorized_client")) 1668 ph->status = TALER_KYCLOGIC_STATUS_FAILED; 1669 else if (0 == strcasecmp (err, 1670 "temporarily_unavailable")) 1671 ph->status = TALER_KYCLOGIC_STATUS_PENDING; 1672 else 1673 ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; 1674 ph->http_status = MHD_HTTP_FORBIDDEN; 1675 body = GNUNET_JSON_PACK ( 1676 GNUNET_JSON_pack_string ("error", 1677 err), 1678 GNUNET_JSON_pack_allow_null ( 1679 GNUNET_JSON_pack_string ("error_details", 1680 desc)), 1681 GNUNET_JSON_pack_allow_null ( 1682 GNUNET_JSON_pack_string ("error_uri", 1683 euri))); 1684 GNUNET_break ( 1685 GNUNET_SYSERR != 1686 templating_build (ph->connection, 1687 &ph->http_status, 1688 "oauth2-authentication-failure", 1689 body, 1690 &ph->response)); 1691 json_decref (body); 1692 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1693 ph); 1694 return ph; 1695 1696 } 1697 1698 ph->eh = curl_easy_init (); 1699 GNUNET_assert (NULL != ph->eh); 1700 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1701 "Requesting OAuth 2.0 data via HTTP POST `%s'\n", 1702 pd->token_url); 1703 GNUNET_assert (CURLE_OK == 1704 curl_easy_setopt (ph->eh, 1705 CURLOPT_URL, 1706 pd->token_url)); 1707 GNUNET_assert (CURLE_OK == 1708 curl_easy_setopt (ph->eh, 1709 CURLOPT_VERBOSE, 1710 1)); 1711 GNUNET_assert (CURLE_OK == 1712 curl_easy_setopt (ph->eh, 1713 CURLOPT_POST, 1714 1)); 1715 { 1716 char *client_id; 1717 char *client_secret; 1718 char *authorization_code; 1719 char *redirect_uri_encoded; 1720 char *hps; 1721 1722 hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto, 1723 sizeof (ph->h_payto)); 1724 { 1725 char *redirect_uri; 1726 1727 GNUNET_asprintf (&redirect_uri, 1728 "%skyc-proof/%s", 1729 ps->exchange_base_url, 1730 &pd->section[strlen ("kyc-provider-")]); 1731 redirect_uri_encoded = TALER_urlencode (redirect_uri); 1732 GNUNET_free (redirect_uri); 1733 } 1734 GNUNET_assert (NULL != redirect_uri_encoded); 1735 client_id = curl_easy_escape (ph->eh, 1736 pd->client_id, 1737 0); 1738 GNUNET_assert (NULL != client_id); 1739 client_secret = curl_easy_escape (ph->eh, 1740 pd->client_secret, 1741 0); 1742 GNUNET_assert (NULL != client_secret); 1743 authorization_code = curl_easy_escape (ph->eh, 1744 code, 1745 0); 1746 GNUNET_assert (NULL != authorization_code); 1747 GNUNET_asprintf (&ph->post_body, 1748 "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code", 1749 client_id, 1750 redirect_uri_encoded, 1751 hps, 1752 client_secret, 1753 authorization_code); 1754 curl_free (authorization_code); 1755 curl_free (client_secret); 1756 GNUNET_free (redirect_uri_encoded); 1757 GNUNET_free (hps); 1758 curl_free (client_id); 1759 } 1760 GNUNET_assert (CURLE_OK == 1761 curl_easy_setopt (ph->eh, 1762 CURLOPT_POSTFIELDS, 1763 ph->post_body)); 1764 GNUNET_assert (CURLE_OK == 1765 curl_easy_setopt (ph->eh, 1766 CURLOPT_FOLLOWLOCATION, 1767 1L)); 1768 /* limit MAXREDIRS to 5 as a simple security measure against 1769 a potential infinite loop caused by a malicious target */ 1770 GNUNET_assert (CURLE_OK == 1771 curl_easy_setopt (ph->eh, 1772 CURLOPT_MAXREDIRS, 1773 5L)); 1774 1775 ph->job = GNUNET_CURL_job_add (ps->curl_ctx, 1776 ph->eh, 1777 &handle_curl_login_finished, 1778 ph); 1779 return ph; 1780 } 1781 1782 1783 /** 1784 * Function to asynchronously return the 404 not found 1785 * page for the webhook. 1786 * 1787 * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *` 1788 */ 1789 static void 1790 wh_return_not_found (void *cls) 1791 { 1792 struct TALER_KYCLOGIC_WebhookHandle *wh = cls; 1793 struct MHD_Response *response; 1794 1795 wh->task = NULL; 1796 response = MHD_create_response_from_buffer_static (0, 1797 ""); 1798 wh->cb (wh->cb_cls, 1799 0LLU, 1800 NULL, 1801 false, 1802 NULL, 1803 NULL, 1804 NULL, 1805 TALER_KYCLOGIC_STATUS_KEEP, 1806 GNUNET_TIME_UNIT_ZERO_ABS, 1807 NULL, 1808 MHD_HTTP_NOT_FOUND, 1809 response); 1810 GNUNET_free (wh); 1811 } 1812 1813 1814 /** 1815 * Check KYC status and return result for Webhook. 1816 * 1817 * @param cls the @e cls of this struct with the plugin-specific state 1818 * @param pd provider configuration details 1819 * @param plc callback to lookup accounts with 1820 * @param plc_cls closure for @a plc 1821 * @param http_method HTTP method used for the webhook 1822 * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array 1823 * @param connection MHD connection object (for HTTP headers) 1824 * @param body HTTP request body, or NULL if not available 1825 * @param cb function to call with the result 1826 * @param cb_cls closure for @a cb 1827 * @return handle to cancel operation early 1828 */ 1829 static struct TALER_KYCLOGIC_WebhookHandle * 1830 oauth2_webhook (void *cls, 1831 const struct TALER_KYCLOGIC_ProviderDetails *pd, 1832 TALER_KYCLOGIC_ProviderLookupCallback plc, 1833 void *plc_cls, 1834 const char *http_method, 1835 const char *const url_path[], 1836 struct MHD_Connection *connection, 1837 const json_t *body, 1838 TALER_KYCLOGIC_WebhookCallback cb, 1839 void *cb_cls) 1840 { 1841 struct PluginState *ps = cls; 1842 struct TALER_KYCLOGIC_WebhookHandle *wh; 1843 1844 (void) pd; 1845 (void) plc; 1846 (void) plc_cls; 1847 (void) http_method; 1848 (void) url_path; 1849 (void) connection; 1850 (void) body; 1851 GNUNET_break_op (0); 1852 wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle); 1853 wh->cb = cb; 1854 wh->cb_cls = cb_cls; 1855 wh->ps = ps; 1856 wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found, 1857 wh); 1858 return wh; 1859 } 1860 1861 1862 /** 1863 * Cancel KYC webhook execution. 1864 * 1865 * @param[in] wh handle of operation to cancel 1866 */ 1867 static void 1868 oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh) 1869 { 1870 GNUNET_SCHEDULER_cancel (wh->task); 1871 GNUNET_free (wh); 1872 } 1873 1874 1875 /** 1876 * Initialize OAuth2.0 KYC logic plugin 1877 * 1878 * @param cls a configuration instance 1879 * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin` 1880 */ 1881 void * 1882 libtaler_plugin_kyclogic_oauth2_init (void *cls); 1883 1884 /* declaration to avoid compiler warning */ 1885 void * 1886 libtaler_plugin_kyclogic_oauth2_init (void *cls) 1887 { 1888 const struct GNUNET_CONFIGURATION_Handle *cfg = cls; 1889 struct TALER_KYCLOGIC_Plugin *plugin; 1890 struct PluginState *ps; 1891 1892 ps = GNUNET_new (struct PluginState); 1893 ps->cfg = cfg; 1894 if (GNUNET_OK != 1895 GNUNET_CONFIGURATION_get_value_string (cfg, 1896 "exchange", 1897 "BASE_URL", 1898 &ps->exchange_base_url)) 1899 { 1900 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1901 "exchange", 1902 "BASE_URL"); 1903 GNUNET_free (ps); 1904 return NULL; 1905 } 1906 ps->curl_ctx 1907 = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1908 &ps->curl_rc); 1909 if (NULL == ps->curl_ctx) 1910 { 1911 GNUNET_break (0); 1912 GNUNET_free (ps->exchange_base_url); 1913 GNUNET_free (ps); 1914 return NULL; 1915 } 1916 ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx); 1917 1918 plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin); 1919 plugin->cls = ps; 1920 plugin->load_configuration 1921 = &oauth2_load_configuration; 1922 plugin->unload_configuration 1923 = &oauth2_unload_configuration; 1924 plugin->initiate 1925 = &oauth2_initiate; 1926 plugin->initiate_cancel 1927 = &oauth2_initiate_cancel; 1928 plugin->proof 1929 = &oauth2_proof; 1930 plugin->proof_cancel 1931 = &oauth2_proof_cancel; 1932 plugin->webhook 1933 = &oauth2_webhook; 1934 plugin->webhook_cancel 1935 = &oauth2_webhook_cancel; 1936 return plugin; 1937 } 1938 1939 1940 /** 1941 * Unload authorization plugin 1942 * 1943 * @param cls a `struct TALER_KYCLOGIC_Plugin` 1944 * @return NULL (always) 1945 */ 1946 void * 1947 libtaler_plugin_kyclogic_oauth2_done (void *cls); 1948 1949 /* declaration to avoid compiler warning */ 1950 void * 1951 libtaler_plugin_kyclogic_oauth2_done (void *cls) 1952 { 1953 struct TALER_KYCLOGIC_Plugin *plugin = cls; 1954 struct PluginState *ps = plugin->cls; 1955 1956 if (NULL != ps->curl_ctx) 1957 { 1958 GNUNET_CURL_fini (ps->curl_ctx); 1959 ps->curl_ctx = NULL; 1960 } 1961 if (NULL != ps->curl_rc) 1962 { 1963 GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc); 1964 ps->curl_rc = NULL; 1965 } 1966 GNUNET_free (ps->exchange_base_url); 1967 GNUNET_free (ps); 1968 GNUNET_free (plugin); 1969 return NULL; 1970 }