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