plugin_kyclogic_oauth2.c (58471B)
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 618 { 619 char *redirect_uri; 620 621 GNUNET_asprintf (&redirect_uri, 622 "%skyc-proof/%s", 623 ps->exchange_base_url, 624 &pd->section[strlen ("kyc-provider-")]); 625 redirect_uri_encoded = TALER_urlencode (redirect_uri); 626 GNUNET_free (redirect_uri); 627 } 628 GNUNET_asprintf (&url, 629 "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s&scope=%s", 630 authorize_url, 631 pd->client_id, 632 redirect_uri_encoded, 633 hps, 634 NULL != pd->scope 635 ? pd->scope 636 : ""); 637 GNUNET_free (redirect_uri_encoded); 638 } 639 ih->cb (ih->cb_cls, 640 TALER_EC_NONE, 641 url, 642 NULL /* unknown user_id here */, 643 legi_s, 644 NULL /* no error */); 645 GNUNET_free (url); 646 GNUNET_free (hps); 647 oauth2_initiate_cancel (ih); 648 } 649 650 651 /** 652 * After we are done with the CURL interaction we 653 * need to update our database state with the information 654 * retrieved. 655 * 656 * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` 657 * @param response_code HTTP response code from server, 0 on hard error 658 * @param response in JSON, NULL if response was not in JSON format 659 */ 660 static void 661 handle_curl_setup_finished (void *cls, 662 long response_code, 663 const void *response) 664 { 665 struct TALER_KYCLOGIC_InitiateHandle *ih = cls; 666 const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; 667 const json_t *j = response; 668 669 ih->job = NULL; 670 switch (response_code) 671 { 672 case 0: 673 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 674 "/setup URL failed to return HTTP response\n"); 675 ih->cb (ih->cb_cls, 676 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, 677 NULL, 678 NULL, 679 NULL, 680 "/setup request to OAuth 2.0 backend returned no response"); 681 oauth2_initiate_cancel (ih); 682 return; 683 case MHD_HTTP_OK: 684 { 685 const char *nonce; 686 struct GNUNET_JSON_Specification spec[] = { 687 GNUNET_JSON_spec_string ("nonce", 688 &nonce), 689 GNUNET_JSON_spec_end () 690 }; 691 enum GNUNET_GenericReturnValue res; 692 const char *emsg; 693 unsigned int line; 694 char *url; 695 696 res = GNUNET_JSON_parse (j, 697 spec, 698 &emsg, 699 &line); 700 if (GNUNET_OK != res) 701 { 702 GNUNET_break_op (0); 703 json_dumpf (j, 704 stderr, 705 JSON_INDENT (2)); 706 ih->cb (ih->cb_cls, 707 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, 708 NULL, 709 NULL, 710 NULL, 711 "Unexpected response from KYC gateway: setup must return a nonce"); 712 oauth2_initiate_cancel (ih); 713 return; 714 } 715 GNUNET_asprintf (&url, 716 "%s/%s", 717 pd->authorize_url, 718 nonce); 719 initiate_with_url (ih, 720 url); 721 GNUNET_free (url); 722 return; 723 } 724 break; 725 default: 726 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 727 "/setup URL returned HTTP status %u\n", 728 (unsigned int) response_code); 729 ih->cb (ih->cb_cls, 730 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, 731 NULL, 732 NULL, 733 NULL, 734 "/setup request to OAuth 2.0 backend returned unexpected HTTP status code"); 735 oauth2_initiate_cancel (ih); 736 return; 737 } 738 } 739 740 741 /** 742 * Logic to asynchronously return the response for how to begin the OAuth2.0 743 * checking process to the client. May first request a dynamic URL via 744 * ``/setup`` if configured to use a client-authenticated setup process. 745 * 746 * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` 747 */ 748 static void 749 initiate_task (void *cls) 750 { 751 struct TALER_KYCLOGIC_InitiateHandle *ih = cls; 752 const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; 753 struct PluginState *ps = pd->ps; 754 CURL *eh; 755 756 ih->task = NULL; 757 if (NULL == pd->setup_url) 758 { 759 initiate_with_url (ih, 760 pd->authorize_url); 761 return; 762 } 763 eh = curl_easy_init (); 764 if (NULL == eh) 765 { 766 GNUNET_break (0); 767 ih->cb (ih->cb_cls, 768 TALER_EC_GENERIC_ALLOCATION_FAILURE, 769 NULL, 770 NULL, 771 NULL, 772 "curl_easy_init() failed"); 773 oauth2_initiate_cancel (ih); 774 return; 775 } 776 GNUNET_assert (CURLE_OK == 777 curl_easy_setopt (eh, 778 CURLOPT_URL, 779 pd->setup_url)); 780 #if DEBUG 781 GNUNET_assert (CURLE_OK == 782 curl_easy_setopt (eh, 783 CURLOPT_VERBOSE, 784 1)); 785 #endif 786 if (NULL == ih->initial_address) 787 { 788 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 789 "Staring OAuth 2.0 without initial address\n"); 790 GNUNET_assert (CURLE_OK == 791 curl_easy_setopt (eh, 792 CURLOPT_POST, 793 1)); 794 GNUNET_assert (CURLE_OK == 795 curl_easy_setopt (eh, 796 CURLOPT_POSTFIELDS, 797 "")); 798 GNUNET_assert (CURLE_OK == 799 curl_easy_setopt (eh, 800 CURLOPT_POSTFIELDSIZE, 801 (long) 0)); 802 } 803 else 804 { 805 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 806 "Staring OAuth 2.0 with initial address\n"); 807 #if DEBUG 808 json_dumpf (ih->initial_address, 809 stderr, 810 JSON_INDENT (2)); 811 fprintf (stderr, 812 "\n"); 813 #endif 814 if (GNUNET_OK != 815 TALER_curl_easy_post (&ih->ctx, 816 eh, 817 ih->initial_address)) 818 { 819 curl_easy_cleanup (eh); 820 ih->cb (ih->cb_cls, 821 TALER_EC_GENERIC_ALLOCATION_FAILURE, 822 NULL, 823 NULL, 824 NULL, 825 "TALER_curl_easy_post() failed"); 826 oauth2_initiate_cancel (ih); 827 return; 828 } 829 } 830 GNUNET_assert (CURLE_OK == 831 curl_easy_setopt (eh, 832 CURLOPT_FOLLOWLOCATION, 833 1L)); 834 GNUNET_assert (CURLE_OK == 835 curl_easy_setopt (eh, 836 CURLOPT_MAXREDIRS, 837 5L)); 838 ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx, 839 eh, 840 ih->ctx.headers, 841 &handle_curl_setup_finished, 842 ih); 843 { 844 char *hdr; 845 struct curl_slist *slist; 846 847 GNUNET_asprintf (&hdr, 848 "%s: Bearer %s", 849 MHD_HTTP_HEADER_AUTHORIZATION, 850 pd->client_secret); 851 slist = curl_slist_append (NULL, 852 hdr); 853 GNUNET_CURL_extend_headers (ih->job, 854 slist); 855 curl_slist_free_all (slist); 856 GNUNET_free (hdr); 857 } 858 } 859 860 861 /** 862 * Initiate KYC check. 863 * 864 * @param cls the @e cls of this struct with the plugin-specific state 865 * @param pd provider configuration details 866 * @param account_id which account to trigger process for 867 * @param legitimization_uuid unique ID for the legitimization process 868 * @param context additional contextual information for the legi process 869 * @param cb function to call with the result 870 * @param cb_cls closure for @a cb 871 * @return handle to cancel operation early 872 */ 873 static struct TALER_KYCLOGIC_InitiateHandle * 874 oauth2_initiate (void *cls, 875 const struct TALER_KYCLOGIC_ProviderDetails *pd, 876 const struct TALER_NormalizedPaytoHashP *account_id, 877 uint64_t legitimization_uuid, 878 const json_t *context, 879 TALER_KYCLOGIC_InitiateCallback cb, 880 void *cb_cls) 881 { 882 struct TALER_KYCLOGIC_InitiateHandle *ih; 883 884 (void) cls; 885 ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle); 886 ih->legitimization_uuid = legitimization_uuid; 887 ih->cb = cb; 888 ih->cb_cls = cb_cls; 889 ih->h_payto = *account_id; 890 ih->pd = pd; 891 ih->task = GNUNET_SCHEDULER_add_now (&initiate_task, 892 ih); 893 if (NULL != context) 894 { 895 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 896 "Initiating OAuth2 validation with context\n"); 897 #if DEBUG 898 json_dumpf (context, 899 stderr, 900 JSON_INDENT (2)); 901 fprintf (stderr, 902 "\n"); 903 #endif 904 ih->initial_address = json_incref (json_object_get (context, 905 "initial_address")); 906 } 907 return ih; 908 } 909 910 911 /** 912 * Cancel KYC proof. 913 * 914 * @param[in] ph handle of operation to cancel 915 */ 916 static void 917 oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) 918 { 919 if (NULL != ph->ec) 920 { 921 TALER_JSON_external_conversion_stop (ph->ec); 922 ph->ec = NULL; 923 } 924 if (NULL != ph->task) 925 { 926 GNUNET_SCHEDULER_cancel (ph->task); 927 ph->task = NULL; 928 } 929 if (NULL != ph->job) 930 { 931 GNUNET_CURL_job_cancel (ph->job); 932 ph->job = NULL; 933 } 934 if (NULL != ph->response) 935 { 936 MHD_destroy_response (ph->response); 937 ph->response = NULL; 938 } 939 GNUNET_free (ph->provider_user_id); 940 if (NULL != ph->attributes) 941 json_decref (ph->attributes); 942 GNUNET_free (ph->post_body); 943 GNUNET_free (ph); 944 } 945 946 947 /** 948 * Function called to asynchronously return the final 949 * result to the callback. 950 * 951 * @param cls a `struct TALER_KYCLOGIC_ProofHandle` 952 */ 953 static void 954 return_proof_response (void *cls) 955 { 956 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 957 const char *provider_name; 958 959 ph->task = NULL; 960 provider_name = ph->pd->section; 961 if (0 != 962 strncasecmp (provider_name, 963 "KYC-PROVIDER-", 964 strlen ("KYC-PROVIDER-"))) 965 { 966 GNUNET_break (0); 967 } 968 else 969 { 970 provider_name += strlen ("KYC-PROVIDER-"); 971 } 972 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 973 "Returning KYC proof from `%s'\n", 974 provider_name); 975 ph->cb (ph->cb_cls, 976 ph->status, 977 provider_name, 978 ph->provider_user_id, 979 ph->provider_legitimization_id, 980 GNUNET_TIME_relative_to_absolute (ph->pd->validity), 981 ph->attributes, 982 ph->http_status, 983 ph->response); 984 ph->response = NULL; /*Ownership passed to 'ph->cb'!*/ 985 oauth2_proof_cancel (ph); 986 } 987 988 989 /** 990 * Load a @a template and substitute using @a root, returning the result in a 991 * @a reply encoded suitable for the @a connection with the given @a 992 * http_status code. On errors, the @a http_status code 993 * is updated to reflect the type of error encoded in the 994 * @a reply. 995 * 996 * @param connection the connection we act upon 997 * @param[in,out] http_status code to use on success, 998 * set to alternative code on failure 999 * @param template basename of the template to load 1000 * @param root JSON object to pass as the root context 1001 * @param[out] reply where to write the response object 1002 * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued, 1003 * #GNUNET_SYSERR on failure (to queue an error) 1004 */ 1005 static enum GNUNET_GenericReturnValue 1006 templating_build (struct MHD_Connection *connection, 1007 unsigned int *http_status, 1008 const char *template, 1009 const json_t *root, 1010 struct MHD_Response **reply) 1011 { 1012 enum GNUNET_GenericReturnValue ret; 1013 1014 ret = TALER_TEMPLATING_build (connection, 1015 http_status, 1016 template, 1017 NULL, 1018 NULL, 1019 root, 1020 reply); 1021 if (GNUNET_SYSERR != ret) 1022 { 1023 GNUNET_break (MHD_NO != 1024 MHD_add_response_header (*reply, 1025 MHD_HTTP_HEADER_CONTENT_TYPE, 1026 "text/html")); 1027 } 1028 return ret; 1029 } 1030 1031 1032 /** 1033 * The request for @a ph failed. We may have gotten a useful error 1034 * message in @a j. Generate a failure response. 1035 * 1036 * @param[in,out] ph request that failed 1037 * @param j reply from the server (or NULL) 1038 */ 1039 static void 1040 handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph, 1041 const json_t *j) 1042 { 1043 enum GNUNET_GenericReturnValue res; 1044 1045 { 1046 const char *msg; 1047 const char *desc; 1048 struct GNUNET_JSON_Specification spec[] = { 1049 GNUNET_JSON_spec_string ("error", 1050 &msg), 1051 GNUNET_JSON_spec_string ("error_description", 1052 &desc), 1053 GNUNET_JSON_spec_end () 1054 }; 1055 const char *emsg; 1056 unsigned int line; 1057 1058 res = GNUNET_JSON_parse (j, 1059 spec, 1060 &emsg, 1061 &line); 1062 } 1063 1064 if (GNUNET_OK != res) 1065 { 1066 json_t *body; 1067 1068 GNUNET_break_op (0); 1069 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1070 ph->http_status 1071 = MHD_HTTP_BAD_GATEWAY; 1072 body = GNUNET_JSON_PACK ( 1073 GNUNET_JSON_pack_allow_null ( 1074 GNUNET_JSON_pack_object_incref ("server_response", 1075 (json_t *) j)), 1076 GNUNET_JSON_pack_bool ("debug", 1077 ph->pd->debug_mode), 1078 TALER_JSON_pack_ec ( 1079 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1080 GNUNET_assert (NULL != body); 1081 GNUNET_break ( 1082 GNUNET_SYSERR != 1083 templating_build (ph->connection, 1084 &ph->http_status, 1085 "oauth2-authorization-failure-malformed", 1086 body, 1087 &ph->response)); 1088 json_decref (body); 1089 return; 1090 } 1091 ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED; 1092 ph->http_status = MHD_HTTP_FORBIDDEN; 1093 GNUNET_break ( 1094 GNUNET_SYSERR != 1095 templating_build (ph->connection, 1096 &ph->http_status, 1097 "oauth2-authorization-failure", 1098 j, 1099 &ph->response)); 1100 } 1101 1102 1103 /** 1104 * Type of a callback that receives a JSON @a result. 1105 * 1106 * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *` 1107 * @param status_type how did the process die 1108 * @param code termination status code from the process 1109 * @param attr result some JSON result, NULL if we failed to get an JSON output 1110 */ 1111 static void 1112 converted_proof_cb (void *cls, 1113 enum GNUNET_OS_ProcessStatusType status_type, 1114 unsigned long code, 1115 const json_t *attr) 1116 { 1117 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1118 const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; 1119 1120 ph->ec = NULL; 1121 if ( (NULL == attr) || 1122 (0 != code) ) 1123 { 1124 json_t *body; 1125 char *msg; 1126 1127 GNUNET_break_op (0); 1128 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1129 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1130 if (0 != code) 1131 GNUNET_asprintf (&msg, 1132 "Attribute converter exited with status %ld", 1133 code); 1134 else 1135 msg = GNUNET_strdup ( 1136 "Attribute converter response was not in JSON format"); 1137 body = GNUNET_JSON_PACK ( 1138 GNUNET_JSON_pack_string ("converter", 1139 pd->conversion_binary), 1140 GNUNET_JSON_pack_allow_null ( 1141 GNUNET_JSON_pack_object_incref ("attributes", 1142 (json_t *) attr)), 1143 GNUNET_JSON_pack_bool ("debug", 1144 ph->pd->debug_mode), 1145 GNUNET_JSON_pack_string ("message", 1146 msg), 1147 TALER_JSON_pack_ec ( 1148 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1149 GNUNET_free (msg); 1150 GNUNET_break ( 1151 GNUNET_SYSERR != 1152 templating_build (ph->connection, 1153 &ph->http_status, 1154 "oauth2-conversion-failure", 1155 body, 1156 &ph->response)); 1157 json_decref (body); 1158 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1159 ph); 1160 return; 1161 } 1162 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1163 "Attribute conversion output is:\n"); 1164 #if DEBUG 1165 json_dumpf (attr, 1166 stderr, 1167 JSON_INDENT (2)); 1168 fprintf (stderr, 1169 "\n"); 1170 #endif 1171 { 1172 const char *id; 1173 struct GNUNET_JSON_Specification ispec[] = { 1174 GNUNET_JSON_spec_string ("id", 1175 &id), 1176 GNUNET_JSON_spec_end () 1177 }; 1178 enum GNUNET_GenericReturnValue res; 1179 const char *emsg; 1180 unsigned int line; 1181 1182 res = GNUNET_JSON_parse (attr, 1183 ispec, 1184 &emsg, 1185 &line); 1186 if (GNUNET_OK != res) 1187 { 1188 json_t *body; 1189 1190 GNUNET_break_op (0); 1191 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1192 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1193 body = GNUNET_JSON_PACK ( 1194 GNUNET_JSON_pack_string ("converter", 1195 pd->conversion_binary), 1196 GNUNET_JSON_pack_string ("message", 1197 "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"), 1198 GNUNET_JSON_pack_bool ("debug", 1199 ph->pd->debug_mode), 1200 GNUNET_JSON_pack_object_incref ("attributes", 1201 (json_t *) attr), 1202 TALER_JSON_pack_ec ( 1203 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1204 GNUNET_break ( 1205 GNUNET_SYSERR != 1206 templating_build (ph->connection, 1207 &ph->http_status, 1208 "oauth2-conversion-failure", 1209 body, 1210 &ph->response)); 1211 json_decref (body); 1212 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1213 ph); 1214 return; 1215 } 1216 ph->provider_user_id = GNUNET_strdup (id); 1217 } 1218 if (! json_is_string (json_object_get (attr, 1219 "FORM_ID"))) 1220 { 1221 json_t *body; 1222 1223 GNUNET_break_op (0); 1224 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1225 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1226 body = GNUNET_JSON_PACK ( 1227 GNUNET_JSON_pack_string ("converter", 1228 pd->conversion_binary), 1229 GNUNET_JSON_pack_string ("message", 1230 "Missing 'FORM_ID' field in attributes"), 1231 GNUNET_JSON_pack_bool ("debug", 1232 ph->pd->debug_mode), 1233 GNUNET_JSON_pack_object_incref ("attributes", 1234 (json_t *) attr), 1235 TALER_JSON_pack_ec ( 1236 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1237 GNUNET_break ( 1238 GNUNET_SYSERR != 1239 templating_build (ph->connection, 1240 &ph->http_status, 1241 "oauth2-conversion-failure", 1242 body, 1243 &ph->response)); 1244 json_decref (body); 1245 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1246 ph); 1247 return; 1248 } 1249 ph->status = TALER_KYCLOGIC_STATUS_SUCCESS; 1250 ph->response = MHD_create_response_from_buffer_static (0, 1251 ""); 1252 GNUNET_assert (NULL != ph->response); 1253 GNUNET_break (MHD_YES == 1254 MHD_add_response_header ( 1255 ph->response, 1256 MHD_HTTP_HEADER_LOCATION, 1257 ph->pd->post_kyc_redirect_url)); 1258 ph->http_status = MHD_HTTP_SEE_OTHER; 1259 ph->attributes = json_incref ((json_t *) attr); 1260 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1261 ph); 1262 } 1263 1264 1265 /** 1266 * The request for @a ph succeeded (presumably). 1267 * Call continuation with the result. 1268 * 1269 * @param[in,out] ph request that succeeded 1270 * @param j reply from the server 1271 */ 1272 static void 1273 parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, 1274 const json_t *j) 1275 { 1276 const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; 1277 const char *argv[] = { 1278 pd->conversion_binary, 1279 NULL, 1280 }; 1281 1282 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1283 "Calling converter `%s' with JSON\n", 1284 pd->conversion_binary); 1285 #if DEBUG 1286 json_dumpf (j, 1287 stderr, 1288 JSON_INDENT (2)); 1289 #endif 1290 ph->ec = TALER_JSON_external_conversion_start ( 1291 j, 1292 &converted_proof_cb, 1293 ph, 1294 pd->conversion_binary, 1295 argv); 1296 if (NULL != ph->ec) 1297 return; 1298 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1299 "Failed to start OAUTH2 conversion helper `%s'\n", 1300 pd->conversion_binary); 1301 ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; 1302 ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 1303 { 1304 json_t *body; 1305 1306 body = GNUNET_JSON_PACK ( 1307 GNUNET_JSON_pack_string ("converter", 1308 pd->conversion_binary), 1309 GNUNET_JSON_pack_bool ("debug", 1310 ph->pd->debug_mode), 1311 GNUNET_JSON_pack_string ("message", 1312 "Failed to launch KYC conversion helper process."), 1313 TALER_JSON_pack_ec ( 1314 TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)); 1315 GNUNET_break ( 1316 GNUNET_SYSERR != 1317 templating_build (ph->connection, 1318 &ph->http_status, 1319 "oauth2-conversion-failure", 1320 body, 1321 &ph->response)); 1322 json_decref (body); 1323 } 1324 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1325 ph); 1326 } 1327 1328 1329 /** 1330 * After we are done with the CURL interaction we 1331 * need to update our database state with the information 1332 * retrieved. 1333 * 1334 * @param cls our `struct TALER_KYCLOGIC_ProofHandle` 1335 * @param response_code HTTP response code from server, 0 on hard error 1336 * @param response in JSON, NULL if response was not in JSON format 1337 */ 1338 static void 1339 handle_curl_proof_finished (void *cls, 1340 long response_code, 1341 const void *response) 1342 { 1343 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1344 const json_t *j = response; 1345 1346 ph->job = NULL; 1347 switch (response_code) 1348 { 1349 case 0: 1350 { 1351 json_t *body; 1352 1353 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1354 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1355 1356 body = GNUNET_JSON_PACK ( 1357 GNUNET_JSON_pack_string ("message", 1358 "No response from KYC gateway"), 1359 TALER_JSON_pack_ec ( 1360 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1361 GNUNET_break ( 1362 GNUNET_SYSERR != 1363 templating_build (ph->connection, 1364 &ph->http_status, 1365 "oauth2-provider-failure", 1366 body, 1367 &ph->response)); 1368 json_decref (body); 1369 } 1370 break; 1371 case MHD_HTTP_OK: 1372 parse_proof_success_reply (ph, 1373 j); 1374 return; 1375 default: 1376 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1377 "OAuth2.0 info URL returned HTTP status %u\n", 1378 (unsigned int) response_code); 1379 handle_proof_error (ph, 1380 j); 1381 break; 1382 } 1383 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1384 ph); 1385 } 1386 1387 1388 /** 1389 * After we are done with the CURL interaction we 1390 * need to fetch the user's account details. 1391 * 1392 * @param cls our `struct KycProofContext` 1393 * @param response_code HTTP response code from server, 0 on hard error 1394 * @param response in JSON, NULL if response was not in JSON format 1395 */ 1396 static void 1397 handle_curl_login_finished (void *cls, 1398 long response_code, 1399 const void *response) 1400 { 1401 struct TALER_KYCLOGIC_ProofHandle *ph = cls; 1402 const json_t *j = response; 1403 1404 ph->job = NULL; 1405 switch (response_code) 1406 { 1407 case MHD_HTTP_OK: 1408 { 1409 const char *access_token; 1410 const char *token_type; 1411 uint64_t expires_in_s; 1412 const char *refresh_token; 1413 bool no_expires; 1414 bool no_refresh; 1415 struct GNUNET_JSON_Specification spec[] = { 1416 GNUNET_JSON_spec_string ("access_token", 1417 &access_token), 1418 GNUNET_JSON_spec_string ("token_type", 1419 &token_type), 1420 GNUNET_JSON_spec_mark_optional ( 1421 GNUNET_JSON_spec_uint64 ("expires_in", 1422 &expires_in_s), 1423 &no_expires), 1424 GNUNET_JSON_spec_mark_optional ( 1425 GNUNET_JSON_spec_string ("refresh_token", 1426 &refresh_token), 1427 &no_refresh), 1428 GNUNET_JSON_spec_end () 1429 }; 1430 CURL *eh; 1431 1432 { 1433 enum GNUNET_GenericReturnValue res; 1434 const char *emsg; 1435 unsigned int line; 1436 1437 res = GNUNET_JSON_parse (j, 1438 spec, 1439 &emsg, 1440 &line); 1441 if (GNUNET_OK != res) 1442 { 1443 json_t *body; 1444 1445 GNUNET_break_op (0); 1446 ph->http_status 1447 = MHD_HTTP_BAD_GATEWAY; 1448 body = GNUNET_JSON_PACK ( 1449 GNUNET_JSON_pack_object_incref ("server_response", 1450 (json_t *) j), 1451 GNUNET_JSON_pack_bool ("debug", 1452 ph->pd->debug_mode), 1453 GNUNET_JSON_pack_string ("message", 1454 "Unexpected response from KYC gateway: required fields missing or malformed"), 1455 TALER_JSON_pack_ec ( 1456 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1457 GNUNET_break ( 1458 GNUNET_SYSERR != 1459 templating_build (ph->connection, 1460 &ph->http_status, 1461 "oauth2-provider-failure", 1462 body, 1463 &ph->response)); 1464 json_decref (body); 1465 break; 1466 } 1467 } 1468 if (0 != strcasecmp (token_type, 1469 "bearer")) 1470 { 1471 json_t *body; 1472 1473 GNUNET_break_op (0); 1474 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1475 body = GNUNET_JSON_PACK ( 1476 GNUNET_JSON_pack_object_incref ("server_response", 1477 (json_t *) j), 1478 GNUNET_JSON_pack_bool ("debug", 1479 ph->pd->debug_mode), 1480 GNUNET_JSON_pack_string ("message", 1481 "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"), 1482 TALER_JSON_pack_ec ( 1483 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1484 GNUNET_break ( 1485 GNUNET_SYSERR != 1486 templating_build (ph->connection, 1487 &ph->http_status, 1488 "oauth2-provider-failure", 1489 body, 1490 &ph->response)); 1491 json_decref (body); 1492 break; 1493 } 1494 1495 /* We guard against a few characters that could 1496 conceivably be abused to mess with the HTTP header */ 1497 if ( (NULL != strchr (access_token, 1498 '\n')) || 1499 (NULL != strchr (access_token, 1500 '\r')) || 1501 (NULL != strchr (access_token, 1502 ' ')) || 1503 (NULL != strchr (access_token, 1504 ';')) ) 1505 { 1506 json_t *body; 1507 1508 GNUNET_break_op (0); 1509 ph->http_status = MHD_HTTP_BAD_GATEWAY; 1510 body = GNUNET_JSON_PACK ( 1511 GNUNET_JSON_pack_object_incref ("server_response", 1512 (json_t *) j), 1513 GNUNET_JSON_pack_bool ("debug", 1514 ph->pd->debug_mode), 1515 GNUNET_JSON_pack_string ("message", 1516 "Illegal character in access token"), 1517 TALER_JSON_pack_ec ( 1518 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); 1519 GNUNET_break ( 1520 GNUNET_SYSERR != 1521 templating_build (ph->connection, 1522 &ph->http_status, 1523 "oauth2-provider-failure", 1524 body, 1525 &ph->response)); 1526 json_decref (body); 1527 break; 1528 } 1529 1530 eh = curl_easy_init (); 1531 GNUNET_assert (NULL != eh); 1532 GNUNET_assert (CURLE_OK == 1533 curl_easy_setopt (eh, 1534 CURLOPT_URL, 1535 ph->pd->info_url)); 1536 { 1537 char *hdr; 1538 struct curl_slist *slist; 1539 1540 GNUNET_asprintf (&hdr, 1541 "%s: Bearer %s", 1542 MHD_HTTP_HEADER_AUTHORIZATION, 1543 access_token); 1544 slist = curl_slist_append (NULL, 1545 hdr); 1546 ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx, 1547 eh, 1548 slist, 1549 &handle_curl_proof_finished, 1550 ph); 1551 curl_slist_free_all (slist); 1552 GNUNET_free (hdr); 1553 } 1554 return; 1555 } 1556 default: 1557 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1558 "OAuth2.0 login URL returned HTTP status %u\n", 1559 (unsigned int) response_code); 1560 handle_proof_error (ph, 1561 j); 1562 break; 1563 } 1564 return_proof_response (ph); 1565 } 1566 1567 1568 /** 1569 * Check KYC status and return status to human. 1570 * 1571 * @param cls the @e cls of this struct with the plugin-specific state 1572 * @param pd provider configuration details 1573 * @param connection MHD connection object (for HTTP headers) 1574 * @param account_id which account to trigger process for 1575 * @param process_row row in the legitimization processes table the legitimization is for 1576 * @param provider_user_id user ID (or NULL) the proof is for 1577 * @param provider_legitimization_id legitimization ID the proof is for 1578 * @param cb function to call with the result 1579 * @param cb_cls closure for @a cb 1580 * @return handle to cancel operation early 1581 */ 1582 static struct TALER_KYCLOGIC_ProofHandle * 1583 oauth2_proof (void *cls, 1584 const struct TALER_KYCLOGIC_ProviderDetails *pd, 1585 struct MHD_Connection *connection, 1586 const struct TALER_NormalizedPaytoHashP *account_id, 1587 uint64_t process_row, 1588 const char *provider_user_id, 1589 const char *provider_legitimization_id, 1590 TALER_KYCLOGIC_ProofCallback cb, 1591 void *cb_cls) 1592 { 1593 struct PluginState *ps = cls; 1594 struct TALER_KYCLOGIC_ProofHandle *ph; 1595 const char *code; 1596 1597 GNUNET_break (NULL == provider_user_id); 1598 ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle); 1599 GNUNET_snprintf (ph->provider_legitimization_id, 1600 sizeof (ph->provider_legitimization_id), 1601 "%llu", 1602 (unsigned long long) process_row); 1603 if ( (NULL != provider_legitimization_id) && 1604 (0 != strcmp (provider_legitimization_id, 1605 ph->provider_legitimization_id))) 1606 { 1607 GNUNET_break (0); 1608 GNUNET_free (ph); 1609 return NULL; 1610 } 1611 1612 ph->pd = pd; 1613 ph->connection = connection; 1614 ph->h_payto = *account_id; 1615 ph->cb = cb; 1616 ph->cb_cls = cb_cls; 1617 code = MHD_lookup_connection_value (connection, 1618 MHD_GET_ARGUMENT_KIND, 1619 "code"); 1620 if (NULL == code) 1621 { 1622 const char *err; 1623 const char *desc; 1624 const char *euri; 1625 json_t *body; 1626 1627 err = MHD_lookup_connection_value (connection, 1628 MHD_GET_ARGUMENT_KIND, 1629 "error"); 1630 if (NULL == err) 1631 { 1632 GNUNET_break_op (0); 1633 ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING; 1634 ph->http_status = MHD_HTTP_BAD_REQUEST; 1635 body = GNUNET_JSON_PACK ( 1636 GNUNET_JSON_pack_string ("message", 1637 "'code' parameter malformed"), 1638 TALER_JSON_pack_ec ( 1639 TALER_EC_GENERIC_PARAMETER_MALFORMED)); 1640 GNUNET_break ( 1641 GNUNET_SYSERR != 1642 templating_build (ph->connection, 1643 &ph->http_status, 1644 "oauth2-bad-request", 1645 body, 1646 &ph->response)); 1647 json_decref (body); 1648 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1649 ph); 1650 return ph; 1651 } 1652 desc = MHD_lookup_connection_value (connection, 1653 MHD_GET_ARGUMENT_KIND, 1654 "error_description"); 1655 euri = MHD_lookup_connection_value (connection, 1656 MHD_GET_ARGUMENT_KIND, 1657 "error_uri"); 1658 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1659 "OAuth2 process %llu failed with error `%s'\n", 1660 (unsigned long long) process_row, 1661 err); 1662 if (0 == strcasecmp (err, 1663 "server_error")) 1664 ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; 1665 else if (0 == strcasecmp (err, 1666 "unauthorized_client")) 1667 ph->status = TALER_KYCLOGIC_STATUS_FAILED; 1668 else if (0 == strcasecmp (err, 1669 "temporarily_unavailable")) 1670 ph->status = TALER_KYCLOGIC_STATUS_PENDING; 1671 else 1672 ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; 1673 ph->http_status = MHD_HTTP_FORBIDDEN; 1674 body = GNUNET_JSON_PACK ( 1675 GNUNET_JSON_pack_string ("error", 1676 err), 1677 GNUNET_JSON_pack_allow_null ( 1678 GNUNET_JSON_pack_string ("error_details", 1679 desc)), 1680 GNUNET_JSON_pack_allow_null ( 1681 GNUNET_JSON_pack_string ("error_uri", 1682 euri))); 1683 GNUNET_break ( 1684 GNUNET_SYSERR != 1685 templating_build (ph->connection, 1686 &ph->http_status, 1687 "oauth2-authentication-failure", 1688 body, 1689 &ph->response)); 1690 json_decref (body); 1691 ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, 1692 ph); 1693 return ph; 1694 1695 } 1696 1697 ph->eh = curl_easy_init (); 1698 GNUNET_assert (NULL != ph->eh); 1699 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1700 "Requesting OAuth 2.0 data via HTTP POST `%s'\n", 1701 pd->token_url); 1702 GNUNET_assert (CURLE_OK == 1703 curl_easy_setopt (ph->eh, 1704 CURLOPT_URL, 1705 pd->token_url)); 1706 GNUNET_assert (CURLE_OK == 1707 curl_easy_setopt (ph->eh, 1708 CURLOPT_VERBOSE, 1709 1)); 1710 GNUNET_assert (CURLE_OK == 1711 curl_easy_setopt (ph->eh, 1712 CURLOPT_POST, 1713 1)); 1714 { 1715 char *client_id; 1716 char *client_secret; 1717 char *authorization_code; 1718 char *redirect_uri_encoded; 1719 char *hps; 1720 1721 hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto, 1722 sizeof (ph->h_payto)); 1723 { 1724 char *redirect_uri; 1725 1726 GNUNET_asprintf (&redirect_uri, 1727 "%skyc-proof/%s", 1728 ps->exchange_base_url, 1729 &pd->section[strlen ("kyc-provider-")]); 1730 redirect_uri_encoded = TALER_urlencode (redirect_uri); 1731 GNUNET_free (redirect_uri); 1732 } 1733 GNUNET_assert (NULL != redirect_uri_encoded); 1734 client_id = curl_easy_escape (ph->eh, 1735 pd->client_id, 1736 0); 1737 GNUNET_assert (NULL != client_id); 1738 client_secret = curl_easy_escape (ph->eh, 1739 pd->client_secret, 1740 0); 1741 GNUNET_assert (NULL != client_secret); 1742 authorization_code = curl_easy_escape (ph->eh, 1743 code, 1744 0); 1745 GNUNET_assert (NULL != authorization_code); 1746 GNUNET_asprintf (&ph->post_body, 1747 "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code", 1748 client_id, 1749 redirect_uri_encoded, 1750 hps, 1751 client_secret, 1752 authorization_code); 1753 curl_free (authorization_code); 1754 curl_free (client_secret); 1755 GNUNET_free (redirect_uri_encoded); 1756 GNUNET_free (hps); 1757 curl_free (client_id); 1758 } 1759 GNUNET_assert (CURLE_OK == 1760 curl_easy_setopt (ph->eh, 1761 CURLOPT_POSTFIELDS, 1762 ph->post_body)); 1763 GNUNET_assert (CURLE_OK == 1764 curl_easy_setopt (ph->eh, 1765 CURLOPT_FOLLOWLOCATION, 1766 1L)); 1767 /* limit MAXREDIRS to 5 as a simple security measure against 1768 a potential infinite loop caused by a malicious target */ 1769 GNUNET_assert (CURLE_OK == 1770 curl_easy_setopt (ph->eh, 1771 CURLOPT_MAXREDIRS, 1772 5L)); 1773 1774 ph->job = GNUNET_CURL_job_add (ps->curl_ctx, 1775 ph->eh, 1776 &handle_curl_login_finished, 1777 ph); 1778 return ph; 1779 } 1780 1781 1782 /** 1783 * Function to asynchronously return the 404 not found 1784 * page for the webhook. 1785 * 1786 * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *` 1787 */ 1788 static void 1789 wh_return_not_found (void *cls) 1790 { 1791 struct TALER_KYCLOGIC_WebhookHandle *wh = cls; 1792 struct MHD_Response *response; 1793 1794 wh->task = NULL; 1795 response = MHD_create_response_from_buffer_static (0, 1796 ""); 1797 wh->cb (wh->cb_cls, 1798 0LLU, 1799 NULL, 1800 false, 1801 NULL, 1802 NULL, 1803 NULL, 1804 TALER_KYCLOGIC_STATUS_KEEP, 1805 GNUNET_TIME_UNIT_ZERO_ABS, 1806 NULL, 1807 MHD_HTTP_NOT_FOUND, 1808 response); 1809 GNUNET_free (wh); 1810 } 1811 1812 1813 /** 1814 * Check KYC status and return result for Webhook. 1815 * 1816 * @param cls the @e cls of this struct with the plugin-specific state 1817 * @param pd provider configuration details 1818 * @param plc callback to lookup accounts with 1819 * @param plc_cls closure for @a plc 1820 * @param http_method HTTP method used for the webhook 1821 * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array 1822 * @param connection MHD connection object (for HTTP headers) 1823 * @param body HTTP request body, or NULL if not available 1824 * @param cb function to call with the result 1825 * @param cb_cls closure for @a cb 1826 * @return handle to cancel operation early 1827 */ 1828 static struct TALER_KYCLOGIC_WebhookHandle * 1829 oauth2_webhook (void *cls, 1830 const struct TALER_KYCLOGIC_ProviderDetails *pd, 1831 TALER_KYCLOGIC_ProviderLookupCallback plc, 1832 void *plc_cls, 1833 const char *http_method, 1834 const char *const url_path[], 1835 struct MHD_Connection *connection, 1836 const json_t *body, 1837 TALER_KYCLOGIC_WebhookCallback cb, 1838 void *cb_cls) 1839 { 1840 struct PluginState *ps = cls; 1841 struct TALER_KYCLOGIC_WebhookHandle *wh; 1842 1843 (void) pd; 1844 (void) plc; 1845 (void) plc_cls; 1846 (void) http_method; 1847 (void) url_path; 1848 (void) connection; 1849 (void) body; 1850 GNUNET_break_op (0); 1851 wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle); 1852 wh->cb = cb; 1853 wh->cb_cls = cb_cls; 1854 wh->ps = ps; 1855 wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found, 1856 wh); 1857 return wh; 1858 } 1859 1860 1861 /** 1862 * Cancel KYC webhook execution. 1863 * 1864 * @param[in] wh handle of operation to cancel 1865 */ 1866 static void 1867 oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh) 1868 { 1869 GNUNET_SCHEDULER_cancel (wh->task); 1870 GNUNET_free (wh); 1871 } 1872 1873 1874 /** 1875 * Initialize OAuth2.0 KYC logic plugin 1876 * 1877 * @param cls a configuration instance 1878 * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin` 1879 */ 1880 void * 1881 libtaler_plugin_kyclogic_oauth2_init (void *cls); 1882 1883 /* declaration to avoid compiler warning */ 1884 void * 1885 libtaler_plugin_kyclogic_oauth2_init (void *cls) 1886 { 1887 const struct GNUNET_CONFIGURATION_Handle *cfg = cls; 1888 struct TALER_KYCLOGIC_Plugin *plugin; 1889 struct PluginState *ps; 1890 1891 ps = GNUNET_new (struct PluginState); 1892 ps->cfg = cfg; 1893 if (GNUNET_OK != 1894 GNUNET_CONFIGURATION_get_value_string (cfg, 1895 "exchange", 1896 "BASE_URL", 1897 &ps->exchange_base_url)) 1898 { 1899 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1900 "exchange", 1901 "BASE_URL"); 1902 GNUNET_free (ps); 1903 return NULL; 1904 } 1905 ps->curl_ctx 1906 = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1907 &ps->curl_rc); 1908 if (NULL == ps->curl_ctx) 1909 { 1910 GNUNET_break (0); 1911 GNUNET_free (ps->exchange_base_url); 1912 GNUNET_free (ps); 1913 return NULL; 1914 } 1915 ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx); 1916 1917 plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin); 1918 plugin->cls = ps; 1919 plugin->load_configuration 1920 = &oauth2_load_configuration; 1921 plugin->unload_configuration 1922 = &oauth2_unload_configuration; 1923 plugin->initiate 1924 = &oauth2_initiate; 1925 plugin->initiate_cancel 1926 = &oauth2_initiate_cancel; 1927 plugin->proof 1928 = &oauth2_proof; 1929 plugin->proof_cancel 1930 = &oauth2_proof_cancel; 1931 plugin->webhook 1932 = &oauth2_webhook; 1933 plugin->webhook_cancel 1934 = &oauth2_webhook_cancel; 1935 return plugin; 1936 } 1937 1938 1939 /** 1940 * Unload authorization plugin 1941 * 1942 * @param cls a `struct TALER_KYCLOGIC_Plugin` 1943 * @return NULL (always) 1944 */ 1945 void * 1946 libtaler_plugin_kyclogic_oauth2_done (void *cls); 1947 1948 /* declaration to avoid compiler warning */ 1949 void * 1950 libtaler_plugin_kyclogic_oauth2_done (void *cls) 1951 { 1952 struct TALER_KYCLOGIC_Plugin *plugin = cls; 1953 struct PluginState *ps = plugin->cls; 1954 1955 if (NULL != ps->curl_ctx) 1956 { 1957 GNUNET_CURL_fini (ps->curl_ctx); 1958 ps->curl_ctx = NULL; 1959 } 1960 if (NULL != ps->curl_rc) 1961 { 1962 GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc); 1963 ps->curl_rc = NULL; 1964 } 1965 GNUNET_free (ps->exchange_base_url); 1966 GNUNET_free (ps); 1967 GNUNET_free (plugin); 1968 return NULL; 1969 }