Changeset 12239 for sites/trunk/common/includes/wporg-sso/wp-plugin.php
- Timestamp:
- 11/11/2022 05:10:24 AM (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/common/includes/wporg-sso/wp-plugin.php
r12232 r12239 78 78 add_action( 'wp_login', array( $this, 'record_last_logged_in' ), 10, 2 ); 79 79 80 add_action( 'login_form_logout', array( $this, 'login_form_logout' ) ); 81 80 82 add_filter( 'salt', array( $this, 'salt' ), 10, 2 ); 81 83 … … 258 260 add_filter( 'register_url', array( $this, 'register_url' ), 20 ); 259 261 260 // Maybe do a Remote SSO login 262 // Maybe do a Remote SSO login/logout 261 263 $this->_maybe_perform_remote_login(); 264 $this->_maybe_perform_remote_logout(); 262 265 263 266 if ( preg_match( '!/wp-signup\.php$!', $_SERVER['REQUEST_URI'] ) ) { … … 274 277 } 275 278 276 // Allow logout on non-dotorg hosts.279 // Allow logout to process. See self::login_form_logout() 277 280 if ( isset( $_GET['action'] ) && empty( $_POST ) && 'logout' == $_GET['action'] ) { 278 if ( ! preg_match( '!wordpress\.org$!', $this->host ) ) { 279 return; 280 } 281 return; 281 282 } 282 283 … … 488 489 489 490 /** 491 * Override the logout process. 492 */ 493 public function login_form_logout() { 494 check_admin_referer( 'log-out' ); 495 496 // The entire remote-logout process isn't needed on local environments. 497 if ( 'local' === wp_get_environment_type() ) { 498 $sso = $this; 499 add_filter( 'logout_redirect', function() use( $sso ) { 500 return $sso->sso_host_url . '/loggedout'; 501 } ); 502 return; 503 } 504 505 $user = wp_get_current_user(); 506 507 // Perform the logout on this network first. 508 wp_logout(); 509 510 // Redirect back to the requested location.. the referer.. or failing that, the current sites front page after it's all done. 511 $logout_redirect = ( wp_unslash( $_REQUEST['redirect_to'] ?? '' ) ?: wp_get_referer() ) ?: home_url( '/' ); 512 $logout_redirect = apply_filters( 'logout_redirect', $logout_redirect, $logout_redirect, $user ); 513 514 $remote_logout_url = add_query_arg( 515 array( 516 'action' => 'remote-logout', 517 'redirect_to' => urlencode( $logout_redirect ), 518 'loggedout_on[]' => urlencode( $this->_get_targetted_host( $this->host ) ), 519 'sso_logout' => urlencode( $this->_generate_remote_token( $user ) ) 520 ), 521 $this->sso_host_url . '/wp-login.php' 522 ); 523 524 $this->_safe_redirect( $remote_logout_url ); 525 exit; 526 } 527 528 /** 490 529 * Redirects the user back to where they came from (or w.org profile) 491 530 */ … … 512 551 $this->_safe_redirect( 'https://wordpress.org/' ); 513 552 } 553 exit; 514 554 } 515 555 … … 527 567 528 568 $remote_token = wp_unslash( $_GET['sso_token'] ); 529 if ( ! is_string( $remote_token ) || 3 !== substr_count( $remote_token, '|' ) ) { 530 wp_die( 'Invalid token.' ); 531 } 532 533 list( $user_id, $sso_hash, $valid_until, $remember_me ) = explode( '|', $remote_token, 4 ); 534 535 $remote_expiration_valid = ( 536 // +/- 5s on a 5s timeout. 537 $valid_until >= ( time() - 5 ) && 538 $valid_until <= ( time() + 10 ) 539 ); 540 541 $valid_remote_hash = false; 542 $user = get_user_by( 'id', $user_id ); 543 if ( $user ) { 544 $valid_remote_hash = hash_equals( 545 $this->_generate_remote_login_hash( $user, $valid_until, $remember_me ), 546 $sso_hash 547 ); 548 } 549 550 if ( $remote_expiration_valid && $valid_remote_hash ) { 551 wp_set_current_user( (int) $user_id ); 552 wp_set_auth_cookie( (int) $user_id, (bool) $remember_me ); 569 $remote_token = $this->_validate_remote_token( $remote_token ); 570 571 // Log the user in if successful. 572 if ( $remote_token && $remote_token['valid'] && $remote_token['user'] ) { 573 wp_set_current_user( $remote_token['user']->ID ); 574 wp_set_auth_cookie( $remote_token['user']->ID, (bool) $remote_token['remember_me'] ); 553 575 } 554 576 … … 590 612 591 613 if ( $user && $this->_is_valid_targeted_domain( $redirect_host ) && ! preg_match( '!wordpress.org$!i', $redirect_host ) ) { 592 593 // Fetch auth cookie parts to find out if the user has selected 'remember me'. 594 $auth_cookie_parts = wp_parse_auth_cookie( '', 'secure_auth' ); 595 596 $valid_until = time() + 5; // Super short timeout. 597 $remember_me = ! empty( $_POST['rememberme'] ) || ( $auth_cookie_parts && $auth_cookie_parts['expiration'] >= ( time() + ( 2 * DAY_IN_SECONDS ) ) ); 598 599 $hash = $this->_generate_remote_login_hash( $user, $valid_until, $remember_me ); 600 $sso_token = $user->ID . '|' . $hash . '|' . $valid_until . '|' . $remember_me; 601 602 $redirect = add_query_arg( 'sso_token', urlencode( $sso_token ), $redirect ); 614 $sso_token = $this->_generate_remote_token( $user ); 615 $redirect = add_query_arg( 'sso_token', urlencode( $sso_token ), $redirect ); 603 616 } 604 617 … … 607 620 608 621 /** 622 * Log out a user from all sites and networks. 623 * 624 * This works by keeping track of the domains logged out on, and redirecting the user to the next 625 * site in self::VALID_HOSTS. Each is only requested once, so the user should experience minimal 626 * redirects. 627 */ 628 protected function _maybe_perform_remote_logout() { 629 if ( empty( $_GET['sso_logout'] ) ) { 630 return; 631 } 632 633 $remote_token = wp_unslash( $_GET['sso_logout'] ); 634 $remote_token = $this->_validate_remote_token( $remote_token ); 635 636 if ( ! $remote_token || ! $remote_token['valid'] ) { 637 return; 638 } 639 $user = $remote_token['user']; 640 641 // If the matching user is logged in, log them out. 642 // If they're logged in as someone else, that's problematic, but we ignore that intentionally. 643 if ( is_user_logged_in() && get_current_user_id() == $user->ID ) { 644 wp_logout(); 645 } 646 647 // Hosts logged out on.. 648 $logged_out_on = (array) $_REQUEST['loggedout_on'] ?? []; 649 $logged_out_on[] = $this->_get_targetted_host( $this->host ); 650 $need_to_logout_on = array_diff( self::VALID_HOSTS, $logged_out_on ); 651 652 // Logged out everywhere, send them over to the logout confirmation screen. 653 if ( ! $need_to_logout_on ) { 654 $final_url = $this->sso_host_url . '/loggedout'; 655 656 if ( ! empty( $_REQUEST['redirect_to'] ) ) { 657 $final_url = add_query_arg( 'redirect_to', $_REQUEST['redirect_to'], $final_url ); 658 } 659 660 $this->_safe_redirect( $final_url ); 661 exit; 662 } 663 664 // Redirect on to the next host in self::VALID_HOSTS to logout from. 665 $logout_redirect = add_query_arg( 666 array( 667 'action' => 'remote-logout', 668 'sso_logout' => urlencode( $this->_generate_remote_token( $user ) ), 669 'redirect_to' => urlencode( wp_unslash( $_REQUEST['redirect_to'] ?? '' ) ), 670 'loggedout_on' => array_values( $logged_out_on ) 671 ), 672 'https://' . reset( $need_to_logout_on ) . '/wp-login.php' 673 ); 674 675 $this->_safe_redirect( $logout_redirect ); 676 exit; 677 } 678 679 /** 680 * Generates a remote token for login/logout. 681 * 682 * @param WP_User $user The User for the token. 683 * @return string The SSO token. 684 */ 685 protected function _generate_remote_token( $user ) { 686 // Use a super-short timeout for the token. It's only going to be used once. 687 $valid_until = time() + self::REMOTE_TOKEN_TIMEOUT; 688 689 /* 690 * Fetch auth cookie parts to find out if the user has selected 'remember me'. 691 * This is only useful for login tokens, but causes no harm for loggout tokens. 692 */ 693 $auth_cookie_parts = wp_parse_auth_cookie( '', 'secure_auth' ); 694 $remember_me = ! empty( $_POST['rememberme'] ) || ( $auth_cookie_parts && $auth_cookie_parts['expiration'] >= ( time() + ( 2 * DAY_IN_SECONDS ) ) ); 695 696 $hash = $this->_generate_remote_token_hash( $user, $valid_until, $remember_me ); 697 $sso_token = $user->ID . '|' . $hash . '|' . $valid_until . '|' . $remember_me; 698 699 return $sso_token; 700 } 701 702 /** 609 703 * Generate a hash for remote-login for non-wordpress.org domains 610 704 */ 611 protected function _generate_remote_ login_hash( $user, $valid_until, $remember_me = false ) {705 protected function _generate_remote_token_hash( $user, $valid_until, $remember_me = false ) { 612 706 // re-use the same frag that Auth cookies use to invalidate sessions. 613 707 $pass_frag = substr( $user->user_pass, 8, 4 ); … … 619 713 620 714 /** 715 * Validates a SSO token is valid. 716 * 717 * @param string $token The raw token from the URL, wp_unslash() it please. 718 * @return array If the token was valid. 719 */ 720 protected function _validate_remote_token( $token ) { 721 if ( ! is_string( $token ) || 3 !== substr_count( $token, '|' ) ) { 722 wp_die( 'Invalid token.' ); 723 } 724 725 list( $user_id, $sso_hash, $valid_until, $remember_me ) = explode( '|', $token, 4 ); 726 727 $expiration_valid = ( 728 // +/- 5s on a 5s timeout. 729 $valid_until >= ( time() - self::REMOTE_TOKEN_TIMEOUT ) && 730 $valid_until <= ( time() + ( self::REMOTE_TOKEN_TIMEOUT * 2 ) ) 731 ); 732 733 $valid_hash = false; 734 $user = get_user_by( 'id', $user_id ); 735 if ( $user ) { 736 $valid_hash = hash_equals( 737 $this->_generate_remote_token_hash( $user, $valid_until, $remember_me ), 738 $sso_hash 739 ); 740 } 741 742 $valid = ( $expiration_valid && $valid_hash ); 743 744 return compact( 745 'valid', 746 'user', 747 'remember_me' 748 ); 749 } 750 751 /** 621 752 * Add a custom salt, defined in the config. 622 753 */ … … 632 763 * Hooked to 'set_auth_cookie' to provide action to the below function, as the 633 764 * `send_auth_cookies` filter used for the below function has no user context. 765 * 766 * @see https://core.trac.wordpress.org/ticket/56971 634 767 */ 635 768 public function maybe_block_auth_cookies_context_provider( $auth_cookie = null, $expire = null, $expiration = null, $user_id = null ) { … … 650 783 * Note: This action provides no context about the request, which is why the context is being 651 784 * provided via the 'set_auth_cookie' filter hook above. 785 * @see https://core.trac.wordpress.org/ticket/56971 652 786 */ 653 787 public function maybe_block_auth_cookies( $send_cookies ) {
Note: See TracChangeset
for help on using the changeset viewer.