Skip to content

Commit 3d811f2

Browse files
Comments: ensure unauthenticated users cannot access the single comment endpoint for notes.
Fix an issue where notes could be accessed by unauthenticated users by using the single comment REST API endpoint and passing the comment ID (`/wp/v2/comments/<ID>`). This fix only affects the `note` type. Props adamsilverstein, peterwilsoncc, westonruter. See #44157. git-svn-id: https://develop.svn.wordpress.org/trunk@61276 602fd350-edb4-49c9-b593-d223f7449a82
1 parent dc6ce7a commit 3d811f2

File tree

2 files changed

+149
-14
lines changed

2 files changed

+149
-14
lines changed

src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,15 @@ public function register_routes() {
123123
* @return true|WP_Error True if the request has read access, error object otherwise.
124124
*/
125125
public function get_items_permissions_check( $request ) {
126-
$is_note = 'note' === $request['type'];
127-
$is_edit_context = 'edit' === $request['context'];
126+
$is_note = 'note' === $request['type'];
127+
$is_edit_context = 'edit' === $request['context'];
128+
$protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
129+
$forbidden_params = array();
128130

129131
if ( ! empty( $request['post'] ) ) {
130132
foreach ( (array) $request['post'] as $post_id ) {
131133
$post = get_post( $post_id );
132134

133-
if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) {
134-
return new WP_Error(
135-
'rest_comment_not_supported_post_type',
136-
__( 'Sorry, this post type does not support notes.' ),
137-
array( 'status' => 403 )
138-
);
139-
}
140-
141135
if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) {
142136
return new WP_Error(
143137
'rest_cannot_read_post',
@@ -151,6 +145,36 @@ public function get_items_permissions_check( $request ) {
151145
array( 'status' => rest_authorization_required_code() )
152146
);
153147
}
148+
149+
if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) {
150+
if ( current_user_can( 'edit_post', $post->ID ) ) {
151+
return new WP_Error(
152+
'rest_comment_not_supported_post_type',
153+
__( 'Sorry, this post type does not support notes.' ),
154+
array( 'status' => 403 )
155+
);
156+
}
157+
158+
foreach ( $protected_params as $param ) {
159+
if ( 'status' === $param ) {
160+
if ( 'approve' !== $request[ $param ] ) {
161+
$forbidden_params[] = $param;
162+
}
163+
} elseif ( 'type' === $param ) {
164+
if ( 'comment' !== $request[ $param ] ) {
165+
$forbidden_params[] = $param;
166+
}
167+
} elseif ( ! empty( $request[ $param ] ) ) {
168+
$forbidden_params[] = $param;
169+
}
170+
}
171+
return new WP_Error(
172+
'rest_forbidden_param',
173+
/* translators: %s: List of forbidden parameters. */
174+
sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ),
175+
array( 'status' => rest_authorization_required_code() )
176+
);
177+
}
154178
}
155179
}
156180

@@ -174,9 +198,6 @@ public function get_items_permissions_check( $request ) {
174198
}
175199

176200
if ( ! current_user_can( 'edit_posts' ) ) {
177-
$protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
178-
$forbidden_params = array();
179-
180201
foreach ( $protected_params as $param ) {
181202
if ( 'status' === $param ) {
182203
if ( 'approve' !== $request[ $param ] ) {
@@ -1890,7 +1911,7 @@ protected function check_read_post_permission( $post, $request ) {
18901911
* @return bool Whether the comment can be read.
18911912
*/
18921913
protected function check_read_permission( $comment, $request ) {
1893-
if ( ! empty( $comment->comment_post_ID ) ) {
1914+
if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) {
18941915
$post = get_post( $comment->comment_post_ID );
18951916
if ( $post ) {
18961917
if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) {

tests/phpunit/tests/rest-api/rest-comments-controller.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4133,4 +4133,118 @@ public function test_get_note_with_children_link() {
41334133
$this->assertStringContainsString( 'status=all', $children[0]['href'] );
41344134
$this->assertStringContainsString( 'type=note', $children[0]['href'] );
41354135
}
4136+
4137+
/**
4138+
* Test retrieving comments by type as authenticated user.
4139+
*
4140+
* @dataProvider data_comment_type_provider
4141+
* @ticket 44157
4142+
*
4143+
* @param string $comment_type The comment type to test.
4144+
* @param int $count The number of comments to create.
4145+
*/
4146+
public function test_get_items_type_arg_authenticated( $comment_type, $count ) {
4147+
wp_set_current_user( self::$admin_id );
4148+
4149+
$args = array(
4150+
'comment_approved' => 1,
4151+
'comment_post_ID' => self::$post_id,
4152+
'user_id' => self::$author_id,
4153+
'comment_type' => $comment_type,
4154+
);
4155+
4156+
// Create comments of the specified type.
4157+
for ( $i = 0; $i < $count; $i++ ) {
4158+
self::factory()->comment->create( $args );
4159+
}
4160+
4161+
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
4162+
$request->set_param( 'type', $comment_type );
4163+
$request->set_param( 'per_page', self::$per_page );
4164+
4165+
$response = rest_get_server()->dispatch( $request );
4166+
$this->assertSame( 200, $response->get_status(), 'Comments endpoint is expected to return a 200 status' );
4167+
4168+
$comments = $response->get_data();
4169+
$expected_count = 'comment' === $comment_type ? $count + self::$total_comments : $count;
4170+
$this->assertCount( $expected_count, $comments, "comment type '{$comment_type}' is expect to have {$expected_count} comments" );
4171+
4172+
// Next, test getting the individual comments.
4173+
foreach ( $comments as $comment ) {
4174+
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment['id'] ) );
4175+
$response = rest_get_server()->dispatch( $request );
4176+
4177+
$this->assertSame( 200, $response->get_status(), 'Individual comment endpoint is expected to return a 200 status' );
4178+
$data = $response->get_data();
4179+
$this->assertSame( $comment_type, $data['type'], "Individual comment is expected to have type '{$comment_type}'" );
4180+
}
4181+
}
4182+
4183+
/**
4184+
* Test retrieving comments by type as unauthenticated user.
4185+
*
4186+
* @dataProvider data_comment_type_provider
4187+
* @ticket 44157
4188+
*
4189+
* @param string $comment_type The comment type to test.
4190+
* @param int $count The number of comments to create.
4191+
*/
4192+
public function test_get_items_type_arg_unauthenticated( $comment_type, $count ) {
4193+
// First, create comments as admin.
4194+
wp_set_current_user( self::$admin_id );
4195+
4196+
$args = array(
4197+
'comment_approved' => 1,
4198+
'comment_post_ID' => self::$post_id,
4199+
'user_id' => self::$author_id,
4200+
'comment_type' => $comment_type,
4201+
);
4202+
4203+
$comments = array();
4204+
4205+
for ( $i = 0; $i < $count; $i++ ) {
4206+
$comments[] = self::factory()->comment->create( $args );
4207+
}
4208+
4209+
// Log out and test as unauthenticated user.
4210+
wp_logout();
4211+
4212+
$request = new WP_REST_Request( 'GET', '/wp/v2/comments' );
4213+
$request->set_param( 'type', $comment_type );
4214+
$request->set_param( 'per_page', self::$per_page );
4215+
4216+
$response = rest_get_server()->dispatch( $request );
4217+
4218+
// Only comments can be retrieved from the /comments (multiple) endpoint when unauthenticated.
4219+
$expected_status = 'comment' === $comment_type ? 200 : 401;
4220+
$this->assertSame( $expected_status, $response->get_status(), 'Comments endpoint did not return the expected status' );
4221+
if ( 'comment' !== $comment_type ) {
4222+
$this->assertErrorResponse( 'rest_forbidden_param', $response, 401, 'Comments endpoint did not return the expected error response for forbidden parameters' );
4223+
}
4224+
4225+
// Individual comments.
4226+
foreach ( $comments as $comment ) {
4227+
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment ) );
4228+
$response = rest_get_server()->dispatch( $request );
4229+
4230+
// Individual comments using the /comments/<id> endpoint can be retrieved by
4231+
// unauthenticated users - except for the 'note' type which is restricted.
4232+
// See https://core.trac.wordpress.org/ticket/44157.
4233+
$this->assertSame( 'note' === $comment_type ? 401 : 200, $response->get_status(), 'Individual comment endpoint did not return the expected status' );
4234+
}
4235+
}
4236+
4237+
/**
4238+
* Data provider for comment type tests.
4239+
*
4240+
* @return array[] Data provider.
4241+
*/
4242+
public function data_comment_type_provider() {
4243+
return array(
4244+
'comment type' => array( 'comment', 5 ),
4245+
'annotation type' => array( 'annotation', 5 ),
4246+
'discussion type' => array( 'discussion', 9 ),
4247+
'note type' => array( 'note', 3 ),
4248+
);
4249+
}
41364250
}

0 commit comments

Comments
 (0)