From 0e6a32878201ba9c39c1f3a444368f71620aaa7a Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Wed, 29 Jun 2016 03:00:54 +0000 Subject: [PATCH] REST API: Include a refreshed nonce in a `X-WP-Nonce` header when responding to an authenticated request. Props adamsilverstein, welcher, markjaquith, aidvu. Fixes #35662. git-svn-id: https://develop.svn.wordpress.org/trunk@37905 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/rest-api.php | 13 ++- tests/phpunit/tests/rest-api/rest-server.php | 89 ++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index ebcf6a91f5..13bfa596ec 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -548,10 +548,12 @@ function rest_output_link_header() { * * @since 4.4.0 * - * @global mixed $wp_rest_auth_cookie + * @global mixed $wp_rest_auth_cookie + * @global WP_REST_Server $wp_rest_server REST server instance. * - * @param WP_Error|mixed $result Error from another authentication handler, null if we should handle it, - * or another value if not. + * @param WP_Error|mixed $result Error from another authentication handler, + * null if we should handle it, or another value + * if not. * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true. */ function rest_cookie_check_errors( $result ) { @@ -559,7 +561,7 @@ function rest_cookie_check_errors( $result ) { return $result; } - global $wp_rest_auth_cookie; + global $wp_rest_auth_cookie, $wp_rest_server; /* * Is cookie authentication being used? (If we get an auth @@ -592,6 +594,9 @@ function rest_cookie_check_errors( $result ) { return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) ); } + // Send a refreshed nonce in header. + $wp_rest_server->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) ); + return true; } diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php index 86170520ba..2140965e68 100644 --- a/tests/phpunit/tests/rest-api/rest-server.php +++ b/tests/phpunit/tests/rest-api/rest-server.php @@ -23,6 +23,7 @@ class Tests_REST_Server extends WP_Test_REST_TestCase { public function tearDown() { // Remove our temporary spy server $GLOBALS['wp_rest_server'] = null; + unset( $_REQUEST['_wpnonce'] ); parent::tearDown(); } @@ -893,4 +894,92 @@ class Tests_REST_Server extends WP_Test_REST_TestCase { public function filter_wp_rest_server_class() { return 'Spy_REST_Server'; } + + /** + * Refreshed nonce should not be present in header when an invalid nonce is passed for logged in user. + * + * @ticket 35662 + */ + public function test_rest_send_refreshed_nonce_invalid_nonce() { + $this->helper_setup_user_for_rest_send_refreshed_nonce_tests(); + + $_REQUEST['_wpnonce'] = 'random invalid nonce'; + + $headers = $this->helper_make_request_and_return_headers_for_rest_send_refreshed_nonce_tests(); + + $this->assertArrayNotHasKey( 'X-WP-Nonce', $headers ); + } + + /** + * Refreshed nonce should be present in header when a valid nonce is + * passed for logged in/anonymous user and not present when nonce is not + * passed. + * + * @ticket 35662 + * + * @dataProvider data_rest_send_refreshed_nonce + * + * @param bool $has_logged_in_user Will there be a logged in user for this test. + * @param bool $has_nonce Are we passing the nonce. + */ + public function test_rest_send_refreshed_nonce( $has_logged_in_user, $has_nonce ) { + if ( true === $has_logged_in_user ) { + $this->helper_setup_user_for_rest_send_refreshed_nonce_tests(); + } + + if ( $has_nonce ) { + $_REQUEST['_wpnonce'] = wp_create_nonce( 'wp_rest' ); + } + + $headers = $this->helper_make_request_and_return_headers_for_rest_send_refreshed_nonce_tests(); + + if ( $has_nonce ) { + $this->assertArrayHasKey( 'X-WP-Nonce', $headers ); + } else { + $this->assertArrayNotHasKey( 'X-WP-Nonce', $headers ); + } + } + + /** + * @return array { + * @type array { + * @type bool $has_logged_in_user Are we registering a user for the test. + * @type bool $has_nonce Is the nonce passed. + * } + * } + */ + function data_rest_send_refreshed_nonce() { + return array( + array( true, true ), + array( true, false ), + array( false, true ), + array( false, false ), + ); + } + + /** + * Helper to setup a users and auth cookie global for the + * rest_send_refreshed_nonce related tests. + */ + protected function helper_setup_user_for_rest_send_refreshed_nonce_tests() { + $author = self::factory()->user->create( array( 'role' => 'author' ) ); + wp_set_current_user( $author ); + + global $wp_rest_auth_cookie; + + $wp_rest_auth_cookie = true; + } + + /** + * Helper to make the request and get the headers for the + * rest_send_refreshed_nonce related tests. + * + * @return array + */ + protected function helper_make_request_and_return_headers_for_rest_send_refreshed_nonce_tests() { + $request = new WP_REST_Request( 'GET', '/', array() ); + $result = $this->server->serve_request( '/' ); + + return $this->server->sent_headers; + } }