From 831fbaa385e052149894dfc2dfb4c94294a4863d Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 17 Nov 2025 14:44:09 -0800 Subject: [PATCH 01/19] Test comment permissions for 44157 --- .../rest-api/rest-comments-controller.php | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index d6e66573596f4..c4ddbaca971d7 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4133,4 +4133,86 @@ public function test_get_note_with_children_link() { $this->assertStringContainsString( 'status=all', $children[0]['href'] ); $this->assertStringContainsString( 'type=note', $children[0]['href'] ); } + /** + * Test comment permissions. + * + * @ticket 44157 + * + * @return void + */ + public function test_get_items_type_arg() { + // Authorized admin user. + wp_set_current_user( self::$admin_id ); + $comment_type_1 = 'annotation'; + $comment_type_2 = 'discussion'; + $comment_type_3 = 'note'; + $args = array( + 'comment_approved' => 1, + 'comment_post_ID' => self::$post_id, + 'user_id' => self::$author_id, + 'post_id' => self::$post_id, + ); + + $count_1 = 5; + $args['comment_type'] = $comment_type_1; + for ( $i = 0; $i < $count_1; $i++ ) { + self::factory()->comment->create( $args ); + } + + $count_2 = 9; + $args['comment_type'] = $comment_type_2; + for ( $i = 0; $i < $count_2; $i++ ) { + self::factory()->comment->create( $args ); + } + + $count_3 = 3; + $args['comment_type'] = $comment_type_3; + for ( $i = 0; $i < $count_3; $i++ ) { + self::factory()->comment->create( $args ); + } + + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'type', $comment_type_1 ); + + // Admin user and no type gets the two comments of comment type 'all' (the default). + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $comments = $response->get_data(); + $this->assertCount( $count_1, $comments ); + + $request->set_param( 'type', $comment_type_2 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $comments = $response->get_data(); + $this->assertCount( $count_2, $comments ); + $comment_type_ids = wp_list_pluck( $comments, 'id' ); // So we can iterate through them later :) . + + $request->set_param( 'type', $comment_type_3 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $comments = $response->get_data(); + $this->assertCount( $count_3, $comments ); + + // Unset the current user. + wp_set_current_user( null ); + + $request->set_param( 'type', 'comments' ); + $request->set_param( 'per_page', self::$per_page ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 401, $response->get_status() ); + $comments = $response->get_data(); + $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); + + $request->set_param( 'type', $comment_type_2 ); + $response = rest_get_server()->dispatch( $request ); + $comments = $response->get_data(); + $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); + + // But the unauthenticated user can see them at their individual endpoints. + foreach ( $comment_type_ids as $comment_type_id ) { + $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_type_id ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 401, $response->get_status() ); + } + } } From 2f0af738a65132a62433367489661f8da7ac61bd Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 17 Nov 2025 15:54:34 -0800 Subject: [PATCH 02/19] check_read_permission - return false early if no user --- .../endpoints/class-wp-rest-comments-controller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index 7fe79b57c1b9b..e126edd7b3537 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -1890,6 +1890,10 @@ protected function check_read_post_permission( $post, $request ) { * @return bool Whether the comment can be read. */ protected function check_read_permission( $comment, $request ) { + if ( 0 === get_current_user_id() ) { + return false; + } + if ( ! empty( $comment->comment_post_ID ) ) { $post = get_post( $comment->comment_post_ID ); if ( $post ) { @@ -1899,10 +1903,6 @@ protected function check_read_permission( $comment, $request ) { } } - if ( 0 === get_current_user_id() ) { - return false; - } - if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) { return false; } From 4b04b7b7543508a539768c7a01a651bf05fecc18 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 17 Nov 2025 15:56:47 -0800 Subject: [PATCH 03/19] test single comment endpoint permissions --- .../rest-api/rest-comments-controller.php | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index c4ddbaca971d7..60d13883995e0 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4137,16 +4137,13 @@ public function test_get_note_with_children_link() { * Test comment permissions. * * @ticket 44157 - * - * @return void */ public function test_get_items_type_arg() { - // Authorized admin user. wp_set_current_user( self::$admin_id ); - $comment_type_1 = 'annotation'; - $comment_type_2 = 'discussion'; - $comment_type_3 = 'note'; - $args = array( + $comment_type_1 = 'annotation'; + $comment_type_2 = 'discussion'; + $note_comment_type = 'note'; + $args = array( 'comment_approved' => 1, 'comment_post_ID' => self::$post_id, 'user_id' => self::$author_id, @@ -4166,7 +4163,7 @@ public function test_get_items_type_arg() { } $count_3 = 3; - $args['comment_type'] = $comment_type_3; + $args['comment_type'] = $note_comment_type; for ( $i = 0; $i < $count_3; $i++ ) { self::factory()->comment->create( $args ); } @@ -4174,7 +4171,6 @@ public function test_get_items_type_arg() { $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); $request->set_param( 'type', $comment_type_1 ); - // Admin user and no type gets the two comments of comment type 'all' (the default). $response = rest_get_server()->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $comments = $response->get_data(); @@ -4185,16 +4181,17 @@ public function test_get_items_type_arg() { $this->assertEquals( 200, $response->get_status() ); $comments = $response->get_data(); $this->assertCount( $count_2, $comments ); - $comment_type_ids = wp_list_pluck( $comments, 'id' ); // So we can iterate through them later :) . + $comment_type_ids = wp_list_pluck( $comments, 'id' ); - $request->set_param( 'type', $comment_type_3 ); + $request->set_param( 'type', $note_comment_type ); $response = rest_get_server()->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $comments = $response->get_data(); $this->assertCount( $count_3, $comments ); + $note_type_ids = wp_list_pluck( $comments, 'id' ); - // Unset the current user. - wp_set_current_user( null ); + // Log out the current user. + wp_logout(); $request->set_param( 'type', 'comments' ); $request->set_param( 'per_page', self::$per_page ); @@ -4203,12 +4200,19 @@ public function test_get_items_type_arg() { $comments = $response->get_data(); $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); - $request->set_param( 'type', $comment_type_2 ); + $request->set_param( 'comment_type', $comment_type_2 ); $response = rest_get_server()->dispatch( $request ); $comments = $response->get_data(); $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); - // But the unauthenticated user can see them at their individual endpoints. + $request->set_param( 'comment_type', $note_comment_type ); + foreach( $note_type_ids as $note_type_id ) { + $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $note_type_id ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 401, $response->get_status() ); + } + + // Custom comment types should also not be visible to unauthenticated users. foreach ( $comment_type_ids as $comment_type_id ) { $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_type_id ) ); $response = rest_get_server()->dispatch( $request ); From 25a4050ad7c88c3dcece7441dec1a793f1c4e3fb Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 17 Nov 2025 16:05:38 -0800 Subject: [PATCH 04/19] phpcbf --- tests/phpunit/tests/rest-api/rest-comments-controller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 60d13883995e0..fe90d215bb73e 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4150,19 +4150,19 @@ public function test_get_items_type_arg() { 'post_id' => self::$post_id, ); - $count_1 = 5; + $count_1 = 5; $args['comment_type'] = $comment_type_1; for ( $i = 0; $i < $count_1; $i++ ) { self::factory()->comment->create( $args ); } - $count_2 = 9; + $count_2 = 9; $args['comment_type'] = $comment_type_2; for ( $i = 0; $i < $count_2; $i++ ) { self::factory()->comment->create( $args ); } - $count_3 = 3; + $count_3 = 3; $args['comment_type'] = $note_comment_type; for ( $i = 0; $i < $count_3; $i++ ) { self::factory()->comment->create( $args ); @@ -4206,7 +4206,7 @@ public function test_get_items_type_arg() { $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); $request->set_param( 'comment_type', $note_comment_type ); - foreach( $note_type_ids as $note_type_id ) { + foreach ( $note_type_ids as $note_type_id ) { $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $note_type_id ) ); $response = rest_get_server()->dispatch( $request ); $this->assertEquals( 401, $response->get_status() ); From ee5c6b58e98db4cc29d17dfb1951d96887e938f3 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 18 Nov 2025 13:06:39 -0800 Subject: [PATCH 05/19] =?UTF-8?q?Only=20apply=20post=20based=20permissions?= =?UTF-8?q?=20to=20=E2=80=98comment=E2=80=99=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../endpoints/class-wp-rest-comments-controller.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index e126edd7b3537..0054f273a4a27 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -1890,11 +1890,7 @@ protected function check_read_post_permission( $post, $request ) { * @return bool Whether the comment can be read. */ protected function check_read_permission( $comment, $request ) { - if ( 0 === get_current_user_id() ) { - return false; - } - - if ( ! empty( $comment->comment_post_ID ) ) { + if ( 'comment' === $comment->comment_type && ! empty( $comment->comment_post_ID ) ) { $post = get_post( $comment->comment_post_ID ); if ( $post ) { if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) { @@ -1903,6 +1899,10 @@ protected function check_read_permission( $comment, $request ) { } } + if ( 0 === get_current_user_id() ) { + return false; + } + if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) { return false; } From 70b364793fc472cfa58da72e0b823a2200743707 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 18 Nov 2025 13:59:56 -0800 Subject: [PATCH 06/19] refactor(tests): split comment type tests and add data provider --- .../rest-api/rest-comments-controller.php | 137 +++++++++++------- 1 file changed, 82 insertions(+), 55 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index fe90d215bb73e..7464b023e9c76 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4134,89 +4134,116 @@ public function test_get_note_with_children_link() { $this->assertStringContainsString( 'type=note', $children[0]['href'] ); } /** - * Test comment permissions. + * Data provider for comment type tests. * + * @return array + */ + public function data_comment_type_provider() { + return array( + 'annotation type' => array( 'annotation', 5 ), + 'discussion type' => array( 'discussion', 9 ), + 'note type' => array( 'note', 3 ), + ); + } + + /** + * Test retrieving comments by type as authenticated user. + * + * @dataProvider data_comment_type_provider * @ticket 44157 + * + * @param string $comment_type The comment type to test. + * @param int $count The number of comments to create. */ - public function test_get_items_type_arg() { + public function test_get_items_type_arg_authenticated( $comment_type, $count ) { wp_set_current_user( self::$admin_id ); - $comment_type_1 = 'annotation'; - $comment_type_2 = 'discussion'; - $note_comment_type = 'note'; - $args = array( + + $args = array( 'comment_approved' => 1, 'comment_post_ID' => self::$post_id, 'user_id' => self::$author_id, - 'post_id' => self::$post_id, + 'comment_type' => $comment_type, ); - $count_1 = 5; - $args['comment_type'] = $comment_type_1; - for ( $i = 0; $i < $count_1; $i++ ) { - self::factory()->comment->create( $args ); - } - - $count_2 = 9; - $args['comment_type'] = $comment_type_2; - for ( $i = 0; $i < $count_2; $i++ ) { - self::factory()->comment->create( $args ); - } - - $count_3 = 3; - $args['comment_type'] = $note_comment_type; - for ( $i = 0; $i < $count_3; $i++ ) { + // Create comments of the specified type. + for ( $i = 0; $i < $count; $i++ ) { self::factory()->comment->create( $args ); } $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); - $request->set_param( 'type', $comment_type_1 ); + $request->set_param( 'type', $comment_type ); $response = rest_get_server()->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); - $comments = $response->get_data(); - $this->assertCount( $count_1, $comments ); - $request->set_param( 'type', $comment_type_2 ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); $comments = $response->get_data(); - $this->assertCount( $count_2, $comments ); - $comment_type_ids = wp_list_pluck( $comments, 'id' ); + $this->assertCount( $count, $comments ); + } - $request->set_param( 'type', $note_comment_type ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); - $comments = $response->get_data(); - $this->assertCount( $count_3, $comments ); - $note_type_ids = wp_list_pluck( $comments, 'id' ); + /** + * Test retrieving comments by type as unauthenticated user. + * + * @dataProvider data_comment_type_provider + * @ticket 44157 + * + * @param string $comment_type The comment type to test. + * @param int $count The number of comments to create. + */ + public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) { + // First, create comments as admin. + wp_set_current_user( self::$admin_id ); - // Log out the current user. + $args = array( + 'comment_approved' => 1, + 'comment_post_ID' => self::$post_id, + 'user_id' => self::$author_id, + 'comment_type' => $comment_type, + ); + + for ( $i = 0; $i < $count; $i++ ) { + self::factory()->comment->create( $args ); + } + + // Log out and test as unauthenticated user. wp_logout(); - $request->set_param( 'type', 'comments' ); + $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); + $request->set_param( 'type', $comment_type ); $request->set_param( 'per_page', self::$per_page ); + $response = rest_get_server()->dispatch( $request ); $this->assertEquals( 401, $response->get_status() ); - $comments = $response->get_data(); $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); + } - $request->set_param( 'comment_type', $comment_type_2 ); - $response = rest_get_server()->dispatch( $request ); - $comments = $response->get_data(); - $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); + /** + * Test retrieving individual comments by type as unauthenticated user. + * + * @dataProvider data_comment_type_provider + * @ticket 44157 + * + * @param string $comment_type The comment type to test. + * @param int $count The number of comments to create (only 1 used). + */ + public function test_get_individual_comment_type_unauthenticated( $comment_type, $count ) { + // Create a single comment as admin. + wp_set_current_user( self::$admin_id ); - $request->set_param( 'comment_type', $note_comment_type ); - foreach ( $note_type_ids as $note_type_id ) { - $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $note_type_id ) ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 401, $response->get_status() ); - } + $args = array( + 'comment_approved' => 1, + 'comment_post_ID' => self::$post_id, + 'user_id' => self::$author_id, + 'comment_type' => $comment_type, + ); - // Custom comment types should also not be visible to unauthenticated users. - foreach ( $comment_type_ids as $comment_type_id ) { - $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_type_id ) ); - $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 401, $response->get_status() ); - } + $comment_id = self::factory()->comment->create( $args ); + + // Log out and test as unauthenticated user. + wp_logout(); + + $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_id ) ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); } } From 05d53d47370335f6270efbe551c027dcc44a74b3 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 18 Nov 2025 14:11:58 -0800 Subject: [PATCH 07/19] improve tests --- .../tests/rest-api/rest-comments-controller.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 7464b023e9c76..0cadb25ab7b27 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4140,6 +4140,7 @@ public function test_get_note_with_children_link() { */ public function data_comment_type_provider() { return array( + 'comment type' => array( 'comment', 5 ), 'annotation type' => array( 'annotation', 5 ), 'discussion type' => array( 'discussion', 9 ), 'note type' => array( 'note', 3 ), @@ -4172,12 +4173,13 @@ public function test_get_items_type_arg_authenticated( $comment_type, $count ) { $request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); $request->set_param( 'type', $comment_type ); + $request->set_param( 'per_page', self::$per_page ); $response = rest_get_server()->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $comments = $response->get_data(); - $this->assertCount( $count, $comments ); + $this->assertCount( 'comment' === $comment_type ? $count + self::$total_comments: $count, $comments ); } /** @@ -4212,8 +4214,10 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) $request->set_param( 'per_page', self::$per_page ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 401, $response->get_status() ); - $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); + $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); + if ( 'comment' !== $comment_type ) { + $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); + } } /** @@ -4244,6 +4248,9 @@ public function test_get_individual_comment_type_unauthenticated( $comment_type, $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_id ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 401, $response->get_status() ); + $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); + if ( 'comment' !== $comment_type ) { + $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); + } } } From 00e446b711cc1bc17bd8821aa6bbc6cca6648f87 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 18 Nov 2025 14:26:25 -0800 Subject: [PATCH 08/19] validate individual comment endpoint returns correct status --- .../rest-api/rest-comments-controller.php | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 0cadb25ab7b27..022554a3034d5 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4180,6 +4180,16 @@ public function test_get_items_type_arg_authenticated( $comment_type, $count ) { $comments = $response->get_data(); $this->assertCount( 'comment' === $comment_type ? $count + self::$total_comments: $count, $comments ); + + // Next, test getting the individual comments. + foreach( $comments as $comment ) { + $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment['id'] ) ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + $this->assertEquals( $comment_type, $data['type'] ); + } } /** @@ -4202,8 +4212,10 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) 'comment_type' => $comment_type, ); + $comments = array(); + for ( $i = 0; $i < $count; $i++ ) { - self::factory()->comment->create( $args ); + $comments[] = self::factory()->comment->create( $args ); } // Log out and test as unauthenticated user. @@ -4218,6 +4230,13 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) if ( 'comment' !== $comment_type ) { $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); } + + // Next, test getting the individual comments. + foreach( $comments as $comment ) { + $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); + } } /** From 6c48f389ebdc4d78d48f1865cf22a92e37a3d382 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 18 Nov 2025 14:37:02 -0800 Subject: [PATCH 09/19] Adjust logic to only impact notes; adjust tests --- .../endpoints/class-wp-rest-comments-controller.php | 2 +- tests/phpunit/tests/rest-api/rest-comments-controller.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index 0054f273a4a27..29859d3e710dd 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -1890,7 +1890,7 @@ protected function check_read_post_permission( $post, $request ) { * @return bool Whether the comment can be read. */ protected function check_read_permission( $comment, $request ) { - if ( 'comment' === $comment->comment_type && ! empty( $comment->comment_post_ID ) ) { + if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) { $post = get_post( $comment->comment_post_ID ); if ( $post ) { if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) { diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 022554a3034d5..e6dda21b1133d 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4226,8 +4226,8 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) $request->set_param( 'per_page', self::$per_page ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); - if ( 'comment' !== $comment_type ) { + $this->assertEquals( 'note' !== $comment_type ? 200 : 401, $response->get_status() ); + if ( 'note' === $comment_type ) { $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); } @@ -4235,7 +4235,7 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) foreach( $comments as $comment ) { $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); + $this->assertEquals( 'note' !== $comment_type ? 200 : 401, $response->get_status() ); } } From 68f39917cabaa8ab84a70dc0991a20d7e2bf9922 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 18 Nov 2025 16:24:03 -0800 Subject: [PATCH 10/19] test cleanup, improve doc blocks --- .../rest-api/rest-comments-controller.php | 46 ++++--------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index e6dda21b1133d..756cafe174535 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4226,50 +4226,24 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) $request->set_param( 'per_page', self::$per_page ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 'note' !== $comment_type ? 200 : 401, $response->get_status() ); - if ( 'note' === $comment_type ) { + + // Only comments can be retrieved from the /comments (multiple) endpoint when unauthenticated. + $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); + if ( 'comment' !== $comment_type ) { $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); } - // Next, test getting the individual comments. + // Individual comments. foreach( $comments as $comment ) { $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 'note' !== $comment_type ? 200 : 401, $response->get_status() ); + + // Individual comments using the /comments/ endpoint can (unexpectedly) be + // retrieved by unauthenticated users - except for the 'note' type which is restricted. + // See https://core.trac.wordpress.org/ticket/44157. + $this->assertEquals( 'note' === $comment_type ? 401 : 200, $response->get_status() ); } } - /** - * Test retrieving individual comments by type as unauthenticated user. - * - * @dataProvider data_comment_type_provider - * @ticket 44157 - * - * @param string $comment_type The comment type to test. - * @param int $count The number of comments to create (only 1 used). - */ - public function test_get_individual_comment_type_unauthenticated( $comment_type, $count ) { - // Create a single comment as admin. - wp_set_current_user( self::$admin_id ); - - $args = array( - 'comment_approved' => 1, - 'comment_post_ID' => self::$post_id, - 'user_id' => self::$author_id, - 'comment_type' => $comment_type, - ); - - $comment_id = self::factory()->comment->create( $args ); - // Log out and test as unauthenticated user. - wp_logout(); - - $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_id ) ); - $response = rest_get_server()->dispatch( $request ); - - $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); - if ( 'comment' !== $comment_type ) { - $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); - } - } } From 0fa0cf291596df8f6016c9e1a2a421fd7311a08f Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 18 Nov 2025 16:27:42 -0800 Subject: [PATCH 11/19] phpcbf --- tests/phpunit/tests/rest-api/rest-comments-controller.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 756cafe174535..ff02d8ba91c64 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4179,10 +4179,10 @@ public function test_get_items_type_arg_authenticated( $comment_type, $count ) { $this->assertEquals( 200, $response->get_status() ); $comments = $response->get_data(); - $this->assertCount( 'comment' === $comment_type ? $count + self::$total_comments: $count, $comments ); + $this->assertCount( 'comment' === $comment_type ? $count + self::$total_comments : $count, $comments ); // Next, test getting the individual comments. - foreach( $comments as $comment ) { + foreach ( $comments as $comment ) { $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment['id'] ) ); $response = rest_get_server()->dispatch( $request ); @@ -4234,7 +4234,7 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) } // Individual comments. - foreach( $comments as $comment ) { + foreach ( $comments as $comment ) { $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment ) ); $response = rest_get_server()->dispatch( $request ); @@ -4244,6 +4244,4 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) $this->assertEquals( 'note' === $comment_type ? 401 : 200, $response->get_status() ); } } - - } From f26746a4206cd52cf82fcf50dd77cffbe718cf5b Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 19 Nov 2025 13:04:55 +1100 Subject: [PATCH 12/19] Do not expose whether post type supports notes to unauthenticated users. --- .../class-wp-rest-comments-controller.php | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index 29859d3e710dd..e00e270ea6fb2 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -123,21 +123,15 @@ public function register_routes() { * @return true|WP_Error True if the request has read access, error object otherwise. */ public function get_items_permissions_check( $request ) { - $is_note = 'note' === $request['type']; - $is_edit_context = 'edit' === $request['context']; + $is_note = 'note' === $request['type']; + $is_edit_context = 'edit' === $request['context']; + $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' ); + $forbidden_params = array(); if ( ! empty( $request['post'] ) ) { foreach ( (array) $request['post'] as $post_id ) { $post = get_post( $post_id ); - if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { - return new WP_Error( - 'rest_comment_not_supported_post_type', - __( 'Sorry, this post type does not support notes.' ), - array( 'status' => 403 ) - ); - } - if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', @@ -151,6 +145,36 @@ public function get_items_permissions_check( $request ) { array( 'status' => rest_authorization_required_code() ) ); } + + if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { + if ( current_user_can( get_post_type_object( $post->post_type )->cap->edit_posts ) ) { + return new WP_Error( + 'rest_comment_not_supported_post_type', + __( 'Sorry, this post type does not support notes.' ), + array( 'status' => 403 ) + ); + } + + foreach ( $protected_params as $param ) { + if ( 'status' === $param ) { + if ( 'approve' !== $request[ $param ] ) { + $forbidden_params[] = $param; + } + } elseif ( 'type' === $param ) { + if ( 'comment' !== $request[ $param ] ) { + $forbidden_params[] = $param; + } + } elseif ( ! empty( $request[ $param ] ) ) { + $forbidden_params[] = $param; + } + } + return new WP_Error( + 'rest_forbidden_param', + /* translators: %s: List of forbidden parameters. */ + sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), + array( 'status' => rest_authorization_required_code() ) + ); + } } } @@ -174,23 +198,6 @@ public function get_items_permissions_check( $request ) { } if ( ! current_user_can( 'edit_posts' ) ) { - $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' ); - $forbidden_params = array(); - - foreach ( $protected_params as $param ) { - if ( 'status' === $param ) { - if ( 'approve' !== $request[ $param ] ) { - $forbidden_params[] = $param; - } - } elseif ( 'type' === $param ) { - if ( 'comment' !== $request[ $param ] ) { - $forbidden_params[] = $param; - } - } elseif ( ! empty( $request[ $param ] ) ) { - $forbidden_params[] = $param; - } - } - if ( ! empty( $forbidden_params ) ) { return new WP_Error( 'rest_forbidden_param', From d734ac4ae5d8498c3eced211e25c91bae8c505e3 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 19 Nov 2025 13:16:50 +1100 Subject: [PATCH 13/19] =?UTF-8?q?Didn=E2=80=99t=20mean=20to=20remove=20thi?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../class-wp-rest-comments-controller.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index e00e270ea6fb2..3b6d29615ecfb 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -198,6 +198,20 @@ public function get_items_permissions_check( $request ) { } if ( ! current_user_can( 'edit_posts' ) ) { + foreach ( $protected_params as $param ) { + if ( 'status' === $param ) { + if ( 'approve' !== $request[ $param ] ) { + $forbidden_params[] = $param; + } + } elseif ( 'type' === $param ) { + if ( 'comment' !== $request[ $param ] ) { + $forbidden_params[] = $param; + } + } elseif ( ! empty( $request[ $param ] ) ) { + $forbidden_params[] = $param; + } + } + if ( ! empty( $forbidden_params ) ) { return new WP_Error( 'rest_forbidden_param', From 875afc716eab42b56346a827c4191135cbb1b750 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 19 Nov 2025 13:20:01 +1100 Subject: [PATCH 14/19] Use assertSame, add messages. --- .../rest-api/rest-comments-controller.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index ff02d8ba91c64..9c245209c1dd5 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4176,19 +4176,20 @@ public function test_get_items_type_arg_authenticated( $comment_type, $count ) { $request->set_param( 'per_page', self::$per_page ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status(), 'Comments endpoint is expected to return a 200 status' ); - $comments = $response->get_data(); - $this->assertCount( 'comment' === $comment_type ? $count + self::$total_comments : $count, $comments ); + $comments = $response->get_data(); + $expected_count = 'comment' === $comment_type ? $count + self::$total_comments : $count; + $this->assertCount( $expected_count, $comments, "comment type '{$comment_type}' is expect to have {$expected_count} comments" ); // Next, test getting the individual comments. foreach ( $comments as $comment ) { $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment['id'] ) ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status(), 'Individual comment endpoint is expected to return a 200 status' ); $data = $response->get_data(); - $this->assertEquals( $comment_type, $data['type'] ); + $this->assertSame( $comment_type, $data['type'], "Individual comment is expected to have type '{$comment_type}'" ); } } @@ -4228,9 +4229,10 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) $response = rest_get_server()->dispatch( $request ); // Only comments can be retrieved from the /comments (multiple) endpoint when unauthenticated. - $this->assertEquals( 'comment' === $comment_type ? 200 : 401, $response->get_status() ); + $expected_status = 'comment' === $comment_type ? 200 : 401; + $this->assertSame( $expected_status, $response->get_status(), 'Comments endpoint did not return the expected status' ); if ( 'comment' !== $comment_type ) { - $this->assertErrorResponse( 'rest_forbidden_param', $response, 401 ); + $this->assertErrorResponse( 'rest_forbidden_param', $response, 401, 'Comments endpoint did not return the expected error response for forbidden parameters' ); } // Individual comments. @@ -4241,7 +4243,7 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) // Individual comments using the /comments/ endpoint can (unexpectedly) be // retrieved by unauthenticated users - except for the 'note' type which is restricted. // See https://core.trac.wordpress.org/ticket/44157. - $this->assertEquals( 'note' === $comment_type ? 401 : 200, $response->get_status() ); + $this->assertSame( 'note' === $comment_type ? 401 : 200, $response->get_status(), 'Individual comment endpoint did not return the expected status' ); } } } From 20addb946a3a2123b275932a94ad7575d544e4ea Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 19 Nov 2025 13:23:42 +1100 Subject: [PATCH 15/19] Nit: I am the worst. --- tests/phpunit/tests/rest-api/rest-comments-controller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 9c245209c1dd5..ac4e7d8567884 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4133,10 +4133,11 @@ public function test_get_note_with_children_link() { $this->assertStringContainsString( 'status=all', $children[0]['href'] ); $this->assertStringContainsString( 'type=note', $children[0]['href'] ); } + /** * Data provider for comment type tests. * - * @return array + * @return array[] Data provider. */ public function data_comment_type_provider() { return array( From 2dd66bcd8e0a04b32a97c7335ab09041bfeab61e Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 19 Nov 2025 14:34:47 -0800 Subject: [PATCH 16/19] docblock cleanup --- tests/phpunit/tests/rest-api/rest-comments-controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index ac4e7d8567884..cd264fa20d2d9 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4241,8 +4241,8 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment ) ); $response = rest_get_server()->dispatch( $request ); - // Individual comments using the /comments/ endpoint can (unexpectedly) be - // retrieved by unauthenticated users - except for the 'note' type which is restricted. + // Individual comments using the /comments/ endpoint can be retrieved by + // unauthenticated users - except for the 'note' type which is restricted. // See https://core.trac.wordpress.org/ticket/44157. $this->assertSame( 'note' === $comment_type ? 401 : 200, $response->get_status(), 'Individual comment endpoint did not return the expected status' ); } From c6a2ce11043ff91dcf7004f1ba5bc35fde15fcc3 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 20 Nov 2025 08:52:39 -0800 Subject: [PATCH 17/19] Apply suggestion from @westonruter Co-authored-by: Weston Ruter --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index 3b6d29615ecfb..d4ff60279aa28 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -147,7 +147,7 @@ public function get_items_permissions_check( $request ) { } if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { - if ( current_user_can( get_post_type_object( $post->post_type )->cap->edit_posts ) ) { + if ( current_user_can( 'edit_post', $post->ID ) { return new WP_Error( 'rest_comment_not_supported_post_type', __( 'Sorry, this post type does not support notes.' ), From 7e66010234ee3ab31b501e5a32299e7d478ab914 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 20 Nov 2025 13:10:40 -0800 Subject: [PATCH 18/19] missing brace --- .../rest-api/endpoints/class-wp-rest-comments-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index d4ff60279aa28..c96a3fe0ddbf5 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -147,7 +147,7 @@ public function get_items_permissions_check( $request ) { } if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { - if ( current_user_can( 'edit_post', $post->ID ) { + if ( current_user_can( 'edit_post', $post->ID ) ) { return new WP_Error( 'rest_comment_not_supported_post_type', __( 'Sorry, this post type does not support notes.' ), From 9c722ca330fd41480bd949fff755f2202d5b2c04 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Fri, 21 Nov 2025 09:32:00 -0800 Subject: [PATCH 19/19] Move data provider after tests that use it --- .../rest-api/rest-comments-controller.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index cd264fa20d2d9..26da64a7a211e 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4134,20 +4134,6 @@ public function test_get_note_with_children_link() { $this->assertStringContainsString( 'type=note', $children[0]['href'] ); } - /** - * Data provider for comment type tests. - * - * @return array[] Data provider. - */ - public function data_comment_type_provider() { - return array( - 'comment type' => array( 'comment', 5 ), - 'annotation type' => array( 'annotation', 5 ), - 'discussion type' => array( 'discussion', 9 ), - 'note type' => array( 'note', 3 ), - ); - } - /** * Test retrieving comments by type as authenticated user. * @@ -4247,4 +4233,18 @@ public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) $this->assertSame( 'note' === $comment_type ? 401 : 200, $response->get_status(), 'Individual comment endpoint did not return the expected status' ); } } + + /** + * Data provider for comment type tests. + * + * @return array[] Data provider. + */ + public function data_comment_type_provider() { + return array( + 'comment type' => array( 'comment', 5 ), + 'annotation type' => array( 'annotation', 5 ), + 'discussion type' => array( 'discussion', 9 ), + 'note type' => array( 'note', 3 ), + ); + } }