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