Cherry picked sec changes

This commit is contained in:
roykho 2022-02-24 08:49:11 -08:00
parent e204731617
commit 8d57884a70
No known key found for this signature in database
GPG Key ID: 00D6C128DC6E0F71
3 changed files with 185 additions and 5 deletions

View File

@ -1134,9 +1134,19 @@ class WC_Checkout {
*/
public function process_checkout() {
try {
$nonce_value = wc_get_var( $_REQUEST['woocommerce-process-checkout-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // phpcs:ignore
$nonce_value = wc_get_var( $_REQUEST['woocommerce-process-checkout-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // phpcs:ignore
$expiry_message = sprintf(
/* translators: %s: shop cart url */
__( 'Sorry, your session has expired. <a href="%s" class="wc-backward">Return to shop</a>', 'woocommerce' ),
esc_url( wc_get_page_permalink( 'shop' ) )
);
if ( empty( $nonce_value ) || ! wp_verify_nonce( $nonce_value, 'woocommerce-process_checkout' ) ) {
// If the cart is empty, the nonce check failed because of session expiry.
if ( WC()->cart->is_empty() ) {
throw new Exception( $expiry_message );
}
WC()->session->set( 'refresh_totals', true );
throw new Exception( __( 'We were unable to process your order, please try again.', 'woocommerce' ) );
}
@ -1147,8 +1157,7 @@ class WC_Checkout {
do_action( 'woocommerce_before_checkout_process' );
if ( WC()->cart->is_empty() ) {
/* translators: %s: shop cart url */
throw new Exception( sprintf( __( 'Sorry, your session has expired. <a href="%s" class="wc-backward">Return to shop</a>', 'woocommerce' ), esc_url( wc_get_page_permalink( 'shop' ) ) ) );
throw new Exception( $expiry_message );
}
do_action( 'woocommerce_checkout_process' );

View File

@ -88,12 +88,18 @@ class WC_Session_Handler extends WC_Session {
$cookie = $this->get_session_cookie();
if ( $cookie ) {
// Customer ID will be an MD5 hash id this is a guest session.
$this->_customer_id = $cookie[0];
$this->_session_expiration = $cookie[1];
$this->_session_expiring = $cookie[2];
$this->_has_cookie = true;
$this->_data = $this->get_session_data();
if ( ! $this->is_session_cookie_valid() ) {
$this->destroy_session();
$this->set_session_expiration();
}
// If the user logs in, update session.
if ( is_user_logged_in() && strval( get_current_user_id() ) !== $this->_customer_id ) {
$guest_session_id = $this->_customer_id;
@ -115,6 +121,30 @@ class WC_Session_Handler extends WC_Session {
}
}
/**
* Checks if session cookie is expired, or belongs to a logged out user.
*
* @return bool Whether session cookie is valid.
*/
private function is_session_cookie_valid() {
// If session is expired, session cookie is invalid.
if ( time() > $this->_session_expiration ) {
return false;
}
// If user has logged out, session cookie is invalid.
if ( ! is_user_logged_in() && ! $this->is_customer_guest( $this->_customer_id ) ) {
return false;
}
// Session from a different user is not valid. (Although from a guest user will be valid)
if ( is_user_logged_in() && ! $this->is_customer_guest( $this->_customer_id ) && strval( get_current_user_id() ) !== $this->_customer_id ) {
return false;
}
return true;
}
/**
* Sets the session cookie on-demand (usually after adding an item to the cart).
*
@ -181,12 +211,54 @@ class WC_Session_Handler extends WC_Session {
if ( empty( $customer_id ) ) {
require_once ABSPATH . 'wp-includes/class-phpass.php';
$hasher = new PasswordHash( 8, false );
$customer_id = md5( $hasher->get_random_bytes( 32 ) );
$customer_id = 't_' . substr( md5( $hasher->get_random_bytes( 32 ) ), 2 );
}
return $customer_id;
}
/**
* Checks if this is an auto-generated customer ID.
*
* @param string|int $customer_id Customer ID to check.
*
* @return bool Whether customer ID is randomly generated.
*/
private function is_customer_guest( $customer_id ) {
$customer_id = strval( $customer_id );
if ( empty( $customer_id ) ) {
return true;
}
if ( 't_' === substr( $customer_id, 0, 2 ) ) {
return true;
}
/**
* Legacy checks. This is to handle sessions that were created from a previous release.
* Maybe we can get rid of them after a few releases.
*/
// Almost all random $customer_ids will have some letters in it, while all actual ids will be integers.
if ( strval( (int) $customer_id ) !== $customer_id ) {
return true;
}
// Performance hack to potentially save a DB query, when same user as $customer_id is logged in.
if ( is_user_logged_in() && strval( get_current_user_id() ) === $customer_id ) {
return false;
} else {
$customer = new WC_Customer( $customer_id );
if ( 0 === $customer->get_id() ) {
return true;
}
}
return false;
}
/**
* Get session unique ID for requests if session is initialized or user ID if logged in.
* Introduced to help with unit tests.

View File

@ -130,12 +130,111 @@ class WC_Tests_Session_Handler extends WC_Unit_Test_Case {
$this->assertEquals( $this->handler->get_customer_unique_id(), $this->handler->maybe_update_nonce_user_logged_out( 1, 'woocommerce-something' ) );
}
/**
* @testdox Test that session from cookie is destroyed if expired.
*/
public function test_destroy_session_cookie_expired() {
$customer_id = '1';
$session_expiration = time() - 10000;
$session_expiring = time() - 1000;
$cookie_hash = '';
$this->session_key = $customer_id;
$handler = $this
->getMockBuilder( WC_Session_Handler::class )
->setMethods( array( 'get_session_cookie' ) )
->getMock();
$handler
->method( 'get_session_cookie' )
->willReturn( array( $customer_id, $session_expiration, $session_expiring, $cookie_hash ) );
add_filter( 'woocommerce_set_cookie_enabled', '__return_false' );
$handler->init_session_cookie();
remove_filter( 'woocommerce_set_cookie_enabled', '__return_false' );
$this->assertFalse( wp_cache_get( $this->cache_prefix . $this->session_key, WC_SESSION_CACHE_GROUP ) );
$this->assertNull( $this->get_session_from_db( $this->session_key ) );
}
/**
* @testdox Test that session from cookie is destroyed if user is logged out.
*/
public function test_destroy_session_user_logged_out() {
$customer_id = '1';
$session_expiration = time() + 50000;
$session_expiring = time() + 5000;
$cookie_hash = '';
$this->session_key = $customer_id;
// Simulate a log out.
wp_set_current_user( 0 );
$handler = $this
->getMockBuilder( WC_Session_Handler::class )
->setMethods( array( 'get_session_cookie' ) )
->getMock();
$handler
->method( 'get_session_cookie' )
->willReturn( array( $customer_id, $session_expiration, $session_expiring, $cookie_hash ) );
add_filter( 'woocommerce_set_cookie_enabled', '__return_false' );
$handler->init_session_cookie();
remove_filter( 'woocommerce_set_cookie_enabled', '__return_false' );
$this->assertFalse( wp_cache_get( $this->cache_prefix . $this->session_key, WC_SESSION_CACHE_GROUP ) );
$this->assertNull( $this->get_session_from_db( $this->session_key ) );
}
/**
* @testdox Test that session from cookie is destroyed if logged in user doesn't match.
*/
public function test_destroy_session_user_mismatch() {
$customer = WC_Helper_Customer::create_customer();
$customer_id = (string) $customer->get_id();
$session_expiration = time() + 50000;
$session_expiring = time() + 5000;
$cookie_hash = '';
$handler = $this
->getMockBuilder( WC_Session_Handler::class )
->setMethods( array( 'get_session_cookie' ) )
->getMock();
wp_set_current_user( $customer->get_id() );
$handler->init();
$handler->set( 'cart', 'fake cart' );
$handler->save_data();
$handler
->method( 'get_session_cookie' )
->willReturn( array( $customer_id, $session_expiration, $session_expiring, $cookie_hash ) );
wp_set_current_user( 1 );
add_filter( 'woocommerce_set_cookie_enabled', '__return_false' );
$handler->init_session_cookie();
remove_filter( 'woocommerce_set_cookie_enabled', '__return_false' );
$this->assertFalse( wp_cache_get( $this->cache_prefix . $customer_id, WC_SESSION_CACHE_GROUP ) );
$this->assertNull( $this->get_session_from_db( $customer_id ) );
$this->assertNotNull( $this->get_session_from_db( '1' ) );
}
/**
* Helper function to create a WC session and save it to the DB.
*/
protected function create_session() {
$this->handler->init();
wp_set_current_user( 1 );
$this->handler->init();
$this->handler->set( 'cart', 'fake cart' );
$this->handler->save_data();
$this->session_key = $this->handler->get_customer_id();