From 50e9639f227078d49a26a0f2866a2f418aa2a19d Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Wed, 25 Apr 2018 13:05:48 +0000 Subject: [PATCH] REST API: Add `who=authors` as a query parameter for `GET wp/v2/users`. Any WordPress user who can `edit_posts` of a post type with `show_in_rest=true` can query for authors. This maps to current WordPress behavior where a WordPress user who can view the Manage Posts view for a post type can see any WordPress user assigned to a post (whether published or draft). This implementation, over restricting `who=authors` to users with `list_users`, gives us future flexibility in displaying lists of posts. It still respects more restrictive permissions for `context=edit`. Props danielbachhuber. Fixes #42202. git-svn-id: https://develop.svn.wordpress.org/trunk@43001 602fd350-edb4-49c9-b593-d223f7449a82 --- .../class-wp-rest-users-controller.php | 25 +++++++- .../tests/rest-api/rest-users-controller.php | 64 ++++++++++++++++--- tests/qunit/fixtures/wp-api-generated.js | 8 +++ 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php index 8b03e80d9b..78062617fd 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php @@ -192,6 +192,19 @@ class WP_REST_Users_Controller extends WP_REST_Controller { return new WP_Error( 'rest_forbidden_orderby', __( 'Sorry, you are not allowed to order users by this parameter.' ), array( 'status' => rest_authorization_required_code() ) ); } + if ( 'authors' === $request['who'] ) { + $can_view = false; + $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); + foreach ( $types as $type ) { + if ( current_user_can( $type->cap->edit_posts ) ) { + $can_view = true; + } + } + if ( ! $can_view ) { + return new WP_Error( 'rest_forbidden_who', __( 'Sorry, you are not allowed to query users by this parameter.' ), array( 'status' => rest_authorization_required_code() ) ); + } + } + return true; } @@ -256,7 +269,9 @@ class WP_REST_Users_Controller extends WP_REST_Controller { $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ]; } - if ( ! current_user_can( 'list_users' ) ) { + if ( isset( $registered['who'] ) && ! empty( $request['who'] ) && 'authors' === $request['who'] ) { + $prepared_args['who'] = 'authors'; + } elseif ( ! current_user_can( 'list_users' ) ) { $prepared_args['has_published_posts'] = get_post_types( array( 'show_in_rest' => true ), 'names' ); } @@ -1372,6 +1387,14 @@ class WP_REST_Users_Controller extends WP_REST_Controller { ), ); + $query_params['who'] = array( + 'description' => __( 'Limit result set to users who are considered authors.' ), + 'type' => 'string', + 'enum' => array( + 'authors', + ), + ); + /** * Filter collection parameters for the users controller. * diff --git a/tests/phpunit/tests/rest-api/rest-users-controller.php b/tests/phpunit/tests/rest-api/rest-users-controller.php index 8c43f36089..77b4474cf7 100644 --- a/tests/phpunit/tests/rest-api/rest-users-controller.php +++ b/tests/phpunit/tests/rest-api/rest-users-controller.php @@ -14,6 +14,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { protected static $user; protected static $editor; protected static $draft_editor; + protected static $subscriber; protected static $authors = array(); protected static $posts = array(); protected static $site; @@ -42,6 +43,13 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { 'user_email' => 'draft-editor@example.com', ) ); + self::$subscriber = $factory->user->create( + array( + 'role' => 'subscriber', + 'display_name' => 'subscriber', + 'user_email' => 'subscriber@example.com', + ) + ); foreach ( array( true, false ) as $show_in_rest ) { foreach ( array( true, false ) as $public ) { @@ -166,6 +174,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { 'roles', 'search', 'slug', + 'who', ), $keys ); } @@ -280,7 +289,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); $response = rest_get_server()->dispatch( $request ); $headers = $response->get_headers(); - $this->assertEquals( 53, $headers['X-WP-Total'] ); + $this->assertEquals( 54, $headers['X-WP-Total'] ); $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); $next_link = add_query_arg( array( @@ -299,7 +308,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $request->set_param( 'page', 3 ); $response = rest_get_server()->dispatch( $request ); $headers = $response->get_headers(); - $this->assertEquals( 54, $headers['X-WP-Total'] ); + $this->assertEquals( 55, $headers['X-WP-Total'] ); $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); $prev_link = add_query_arg( array( @@ -318,7 +327,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $request->set_param( 'page', 6 ); $response = rest_get_server()->dispatch( $request ); $headers = $response->get_headers(); - $this->assertEquals( 54, $headers['X-WP-Total'] ); + $this->assertEquals( 55, $headers['X-WP-Total'] ); $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); $prev_link = add_query_arg( array( @@ -332,7 +341,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $request->set_param( 'page', 8 ); $response = rest_get_server()->dispatch( $request ); $headers = $response->get_headers(); - $this->assertEquals( 54, $headers['X-WP-Total'] ); + $this->assertEquals( 55, $headers['X-WP-Total'] ); $this->assertEquals( 6, $headers['X-WP-TotalPages'] ); $prev_link = add_query_arg( array( @@ -525,12 +534,12 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { public function test_get_items_offset() { wp_set_current_user( self::$user ); - // 7 users created in wpSetUpBeforeClass(), plus default user. + // 9 users created in wpSetUpBeforeClass(), plus default user. $this->factory->user->create(); $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); $request->set_param( 'offset', 1 ); $response = rest_get_server()->dispatch( $request ); - $this->assertCount( 9, $response->get_data() ); + $this->assertCount( 10, $response->get_data() ); // 'offset' works with 'per_page' $request->set_param( 'per_page', 2 ); $response = rest_get_server()->dispatch( $request ); @@ -744,9 +753,9 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $request->set_param( 'roles', 'author,subscriber' ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals( 2, count( $data ) ); - $this->assertEquals( $tango, $data[0]['id'] ); - $this->assertEquals( $yolo, $data[1]['id'] ); + $this->assertEquals( 3, count( $data ) ); + $this->assertEquals( $tango, $data[1]['id'] ); + $this->assertEquals( $yolo, $data[2]['id'] ); $request->set_param( 'roles', 'author' ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); @@ -784,6 +793,43 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $this->assertEquals( array(), $data ); } + public function test_get_items_who_author_query() { + wp_set_current_user( self::$superadmin ); + // First request should include subscriber in the set. + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'search', 'subscriber' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertCount( 1, $response->get_data() ); + // Second request should exclude subscriber. + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'who', 'authors' ); + $request->set_param( 'search', 'subscriber' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertCount( 0, $response->get_data() ); + } + + public function test_get_items_who_invalid_query() { + wp_set_current_user( self::$user ); + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'who', 'editor' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Any user with 'edit_posts' on a show_in_rest post type + * can view authors. Others (e.g. subscribers) cannot. + */ + public function test_get_items_who_unauthorized_query() { + wp_set_current_user( self::$subscriber ); + $request = new WP_REST_Request( 'GET', '/wp/v2/users' ); + $request->set_param( 'who', 'authors' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_forbidden_who', $response, 403 ); + } + public function test_get_item() { $user_id = $this->factory->user->create(); wp_set_current_user( self::$user ); diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 6bfb11603f..fd20726c9d 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -2512,6 +2512,14 @@ mockedApiResponse.Schema = { "items": { "type": "string" } + }, + "who": { + "required": false, + "enum": [ + "authors" + ], + "description": "Limit result set to users who are considered authors.", + "type": "string" } } },