<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[61297] branches/6.9/tests/phpunit/tests/rest-api/rest-comments-controller.php: Comments: ensure unauthenticated users cannot access the single comment endpoint for notes.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/61297">61297</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/61297","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>peterwilsoncc</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2025-11-25 01:20:13 +0000 (Tue, 25 Nov 2025)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>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 <a href="https://core.trac.wordpress.org/changeset/61276">[61276]</a> to the 6.9 branch.
Props adamsilverstein, peterwilsoncc, westonruter, tharsheblows.
See <a href="https://core.trac.wordpress.org/ticket/44157">#44157</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#branches69srcwpincludesrestapiendpointsclasswprestcommentscontrollerphp">branches/6.9/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php</a></li>
<li><a href="#branches69testsphpunittestsrestapirestcommentscontrollerphp">branches/6.9/tests/phpunit/tests/rest-api/rest-comments-controller.php</a></li>
</ul>
<h3>Property Changed</h3>
<ul>
<li><a href="#branches69">branches/6.9/</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<span class="cx" style="display: block; padding: 0 10px">Index: branches/6.9
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- branches/6.9 2025-11-25 00:52:06 UTC (rev 61296)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ branches/6.9 2025-11-25 01:20:13 UTC (rev 61297)
</ins><a id="branches69"></a>
<div class="propset"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Property changes: branches/6.9</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnmergeinfo"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: svn:mergeinfo</h4></div>
<span class="cx" style="display: block; padding: 0 10px"> /branches/5.5:49373-49379,49381
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/5.8:51889
</span><span class="cx" style="display: block; padding: 0 10px"> /branches/6.7:59437
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/trunk:61223,61225,61244,61246,61248-61249,61253,61257-61258,61261-61262,61273,61285,61290,61294
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/trunk:61223,61225,61244,61246,61248-61249,61253,61257-61258,61261-61262,61273,61276,61285,61290,61294
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="branches69srcwpincludesrestapiendpointsclasswprestcommentscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/6.9/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/6.9/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php 2025-11-25 00:52:06 UTC (rev 61296)
+++ branches/6.9/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php 2025-11-25 01:20:13 UTC (rev 61297)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -123,21 +123,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @return true|WP_Error True if the request has read access, error object otherwise.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function get_items_permissions_check( $request ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $is_note = 'note' === $request['type'];
- $is_edit_context = 'edit' === $request['context'];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $is_note = 'note' === $request['type'];
+ $is_edit_context = 'edit' === $request['context'];
+ $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
+ $forbidden_params = array();
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! empty( $request['post'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> foreach ( (array) $request['post'] as $post_id ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $post = get_post( $post_id );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 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 )
- );
- }
-
</del><span class="cx" style="display: block; padding: 0 10px"> if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> return new WP_Error(
</span><span class="cx" style="display: block; padding: 0 10px"> 'rest_cannot_read_post',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -151,6 +145,36 @@
</span><span class="cx" style="display: block; padding: 0 10px"> array( 'status' => rest_authorization_required_code() )
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) {
+ 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.' ),
+ 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() )
+ );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -174,9 +198,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! current_user_can( 'edit_posts' ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
- $forbidden_params = array();
-
</del><span class="cx" style="display: block; padding: 0 10px"> foreach ( $protected_params as $param ) {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( 'status' === $param ) {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( 'approve' !== $request[ $param ] ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1890,7 +1911,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @return bool Whether the comment can be read.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> protected function check_read_permission( $comment, $request ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( ! empty( $comment->comment_post_ID ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> $post = get_post( $comment->comment_post_ID );
</span><span class="cx" style="display: block; padding: 0 10px"> if ( $post ) {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) {
</span></span></pre></div>
<a id="branches69testsphpunittestsrestapirestcommentscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/6.9/tests/phpunit/tests/rest-api/rest-comments-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/6.9/tests/phpunit/tests/rest-api/rest-comments-controller.php 2025-11-25 00:52:06 UTC (rev 61296)
+++ branches/6.9/tests/phpunit/tests/rest-api/rest-comments-controller.php 2025-11-25 01:20:13 UTC (rev 61297)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4133,4 +4133,118 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertStringContainsString( 'status=all', $children[0]['href'] );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertStringContainsString( 'type=note', $children[0]['href'] );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /**
+ * 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_authenticated( $comment_type, $count ) {
+ 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,
+ );
+
+ // 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 );
+ $request->set_param( 'per_page', self::$per_page );
+
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status(), 'Comments endpoint is expected to return a 200 status' );
+
+ $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->assertSame( 200, $response->get_status(), 'Individual comment endpoint is expected to return a 200 status' );
+ $data = $response->get_data();
+ $this->assertSame( $comment_type, $data['type'], "Individual comment is expected to have type '{$comment_type}'" );
+ }
+ }
+
+ /**
+ * 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 );
+
+ $args = array(
+ 'comment_approved' => 1,
+ 'comment_post_ID' => self::$post_id,
+ 'user_id' => self::$author_id,
+ 'comment_type' => $comment_type,
+ );
+
+ $comments = array();
+
+ for ( $i = 0; $i < $count; $i++ ) {
+ $comments[] = self::factory()->comment->create( $args );
+ }
+
+ // Log out and test as unauthenticated user.
+ wp_logout();
+
+ $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 );
+
+ // Only comments can be retrieved from the /comments (multiple) endpoint when unauthenticated.
+ $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, 'Comments endpoint did not return the expected error response for forbidden parameters' );
+ }
+
+ // Individual comments.
+ foreach ( $comments as $comment ) {
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment ) );
+ $response = rest_get_server()->dispatch( $request );
+
+ // Individual comments using the /comments/<id> 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' );
+ }
+ }
+
+ /**
+ * 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 ),
+ );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>
</body>
</html>