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