From 46482cd1cf29a2e236b98bde187e1bc59253ca89 Mon Sep 17 00:00:00 2001 From: Jonny Harris Date: Mon, 18 Jul 2022 10:28:07 +0100 Subject: [PATCH] Update patch. --- .../class-wp-rest-settings-controller.php | 83 ++++++- .../rest-api/rest-settings-controller.php | 223 ++++++++++++++++++ 2 files changed, 303 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php index 157c49acbe01e..094613a556e4b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php @@ -49,7 +49,7 @@ public function register_routes() { 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) @@ -66,6 +66,46 @@ public function register_routes() { * @return bool True if the request has read access for the item, otherwise false. */ public function get_item_permissions_check( $request ) { + $options = $this->get_registered_options(); + + foreach ( $options as $name => $args ) { + $test = call_user_func_array( $args['read_permission_callback'], array( $name ) ); + if ( $test ) { + return true; + } + } + + return current_user_can( 'manage_options' ); + } + + /** + * Checks if a request has access to update the specified settings. + * + * @since 6.1.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. + */ + public function update_item_permissions_check( $request ) { + $options = $this->get_registered_options(); + + foreach ( $options as $name => $args ) { + if ( isset( $request[ $name ] ) ) { + $has_permissions = call_user_func_array( $args['edit_permission_callback'], array( $name ) ); + if ( ! $has_permissions ) { + return new WP_Error( + 'rest_cannot_update', + /* translators: %s: Custom field key. */ + sprintf( __( 'Sorry, you are not allowed to edit the %s option.' ), $name ), + array( + 'key' => $name, + 'status' => rest_authorization_required_code(), + ) + ); + } + } + } + return current_user_can( 'manage_options' ); } @@ -82,6 +122,11 @@ public function get_item( $request ) { $response = array(); foreach ( $options as $name => $args ) { + $test = call_user_func_array( $args['read_permission_callback'], array( $name ) ); + if ( ! $test ) { + continue; + } + /** * Filters the value of a setting recognized by the REST API. * @@ -230,12 +275,22 @@ protected function get_registered_options() { } $defaults = array( - 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name, - 'schema' => array(), + 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name, + 'schema' => array(), + 'edit_permission_callback' => array( $this, 'default_edit_permission_callback' ), + 'read_permission_callback' => array( $this, 'default_read_permission_callback' ), ); $rest_args = array_merge( $defaults, $rest_args ); + if ( ! is_callable( $rest_args['edit_permission_callback'] ) ) { + $rest_args['edit_permission_callback'] = array( $this, 'default_edit_permission_callback' ); + } + + if ( ! is_callable( $rest_args['read_permission_callback'] ) ) { + $rest_args['read_permission_callback'] = array( $this, 'default_read_permission_callback' ); + } + $default_schema = array( 'type' => empty( $args['type'] ) ? null : $args['type'], 'description' => empty( $args['description'] ) ? '' : $args['description'], @@ -299,6 +354,28 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } + /** + * Default edit permission callback. + * + * @since 6.1.0 + * + * @return bool + */ + public function default_edit_permission_callback() { + return current_user_can( 'manage_options' ); + } + + /** + * Default read permission callback. + * + * @since 6.1.0 + * + * @return bool + */ + public function default_read_permission_callback() { + return current_user_can( 'manage_options' ); + } + /** * Custom sanitize callback used for all options to allow the use of 'null'. * diff --git a/tests/phpunit/tests/rest-api/rest-settings-controller.php b/tests/phpunit/tests/rest-api/rest-settings-controller.php index 3c11935414af1..8d7129768669e 100644 --- a/tests/phpunit/tests/rest-api/rest-settings-controller.php +++ b/tests/phpunit/tests/rest-api/rest-settings-controller.php @@ -351,6 +351,229 @@ public function test_get_item_with_invalid_value_array_in_options() { $this->assertNull( $data['mycustomsettinginrest'] ); } + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::get_items + */ + public function test_get_item_with_read_permission_callbacks() { + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'read_permission_callback' => '__return_true', + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertArrayHasKey( 'mycustomsettinginrest', $data ); + $this->assertSame( 'hello world', $data['mycustomsettinginrest'] ); + } + + + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::get_item_permissions_check + */ + public function test_get_item_with_read_permission_permission() { + wp_set_current_user( self::$administrator ); + $user = get_user_by( 'id', self::$administrator ); + $user->add_cap( 'custom_rest_cap' ); + + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'read_permission_callback' => function () { + return current_user_can( 'custom_rest_cap' ); + }, + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertArrayHasKey( 'mycustomsettinginrest', $data ); + $this->assertSame( 'hello world', $data['mycustomsettinginrest'] ); + } + + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::get_item_permissions_check + */ + public function test_get_item_with_read_permission_invalid_callbacks() { + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'read_permission_callback' => 'invalid_callback', + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); + } + + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::get_item_permissions_check + */ + public function test_get_item_with_read_permission_callbacks_return_false() { + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'read_permission_callback' => '__return_false', + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); + } + + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::update_item_permissions_check + */ + public function test_update_item_with_edit_permission_callbacks() { + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'edit_permission_callback' => '__return_true', + 'read_permission_callback' => '__return_true', + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); + $request->set_param( 'mycustomsettinginrest', 'Updated value' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertArrayHasKey( 'mycustomsettinginrest', $data ); + $this->assertSame( 'Updated value', $data['mycustomsettinginrest'] ); + } + + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::update_item_permissions_check + */ + public function test_update_item_with_edit_permission_permission() { + wp_set_current_user( self::$administrator ); + $user = get_user_by( 'id', self::$administrator ); + $user->add_cap( 'custom_rest_cap' ); + + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'edit_permission_callback' => function () { + return current_user_can( 'custom_rest_cap' ); + }, + 'read_permission_callback' => '__return_true', + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); + $request->set_param( 'mycustomsettinginrest', 'Updated value' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertArrayHasKey( 'mycustomsettinginrest', $data ); + $this->assertSame( 'Updated value', $data['mycustomsettinginrest'] ); + } + + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::update_item_permissions_check + */ + public function test_update_item_with_edit_permission_invalid_callbacks() { + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'edit_permission_callback' => 'invalid_callback', + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); + $request->set_param( 'mycustomsettinginrest', 'Updated value' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_cannot_update', $response, 401 ); + } + + /** + * @ticket 48885 + * @covers WP_REST_Settings_Controller::update_item_permissions_check + */ + public function test_update_item_with_edit_permission_callbacks_return_false() { + register_setting( + 'somegroup', + 'mycustomsetting', + array( + 'show_in_rest' => array( + 'name' => 'mycustomsettinginrest', + 'edit_permission_callback' => '__return_false', + ), + 'type' => 'string', + ) + ); + + update_option( 'mycustomsetting', 'hello world' ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); + $request->set_param( 'mycustomsettinginrest', 'Updated value' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_cannot_update', $response, 401 ); + } + public function test_get_item_with_invalid_object_array_in_options() { wp_set_current_user( self::$administrator );