Make WordPress Core

Changeset 61297


Ignore:
Timestamp:
11/25/2025 01:20:13 AM (4 months ago)
Author:
peterwilsoncc
Message:

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.

Reviewed by peterwilsoncc.
Merges [61276] to the 6.9 branch.

Props adamsilverstein, peterwilsoncc, westonruter, tharsheblows.
See #44157.

Location:
branches/6.9
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/6.9

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

    r61199 r61297  
    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 );
    132 
    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                 }
    140134
    141135                if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) {
     
    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        }
     
    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 ) {
     
    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 ) {
  • branches/6.9/tests/phpunit/tests/rest-api/rest-comments-controller.php

    r61105 r61297  
    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}
Note: See TracChangeset for help on using the changeset viewer.