Commit fb990bbb1f for wordpress.org
commit fb990bbb1f90294063b036304c496e71bf8a05eb
Author: adamsilverstein <adamsilverstein@git.wordpress.org>
Date: Thu Sep 17 17:47:46 2026 +0000
Comments: Enforce target post permissions when updating notes via REST.
Props adamsilverstein, westonruter, ramonopoly, villanovachile.
Built from https://develop.svn.wordpress.org/trunk@63675
git-svn-id: http://core.svn.wordpress.org/trunk@62849 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
index dfe4946595..8846f72788 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
@@ -560,12 +560,12 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
}
}
- if ( $is_note && ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) {
- return new WP_Error(
- 'rest_cannot_create_note',
- __( 'Sorry, you are not allowed to create notes for this post.' ),
- array( 'status' => rest_authorization_required_code() )
- );
+ if ( $is_note && ! empty( $request['post'] ) ) {
+ $target_check = $this->check_target_post_permission( (int) $request['post'], $request, true );
+
+ if ( is_wp_error( $target_check ) ) {
+ return $target_check;
+ }
}
$edit_cap = $is_note ? array( 'edit_post', (int) $request['post'] ) : array( 'moderate_comments' );
@@ -586,6 +586,16 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
);
}
+ /*
+ * A note's target post was fully checked by check_target_post_permission()
+ * above. Everything below applies to other comments only: a note is allowed
+ * on a draft and on a post whose discussion is closed, and the rest would
+ * repeat what that check already did.
+ */
+ if ( $is_note ) {
+ return true;
+ }
+
$post = get_post( (int) $request['post'] );
if ( ! $post ) {
@@ -596,15 +606,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
);
}
- if ( $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 ( 'draft' === $post->post_status && ! $is_note ) {
+ if ( 'draft' === $post->post_status ) {
return new WP_Error(
'rest_comment_draft_post',
__( 'Sorry, you are not allowed to create a comment on this post.' ),
@@ -628,7 +630,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
);
}
- if ( ! comments_open( $post->ID ) && ! $is_note ) {
+ if ( ! comments_open( $post->ID ) ) {
return new WP_Error(
'rest_comment_closed',
__( 'Sorry, comments are closed for this item.' ),
@@ -862,6 +864,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
* Checks if a given REST request has access to update a comment.
*
* @since 4.7.0
+ * @since 7.1.1 Target post permissions are checked when a comment's parent post is changed.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, error object otherwise.
@@ -880,6 +883,26 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
);
}
+ /*
+ * check_edit_permission() above only establishes that the comment may be
+ * edited where it currently sits, because 'edit_comment' maps to 'edit_post'
+ * on the comment's current parent. When the parent is being changed, the new
+ * parent has to be authorized as well. Without this, a user holding
+ * edit_comment on their own comment or note could reparent it onto any post,
+ * including posts they can neither read nor edit.
+ */
+ if ( isset( $request['post'] ) && (int) $request['post'] !== (int) $comment->comment_post_ID ) {
+ $target_check = $this->check_target_post_permission(
+ (int) $request['post'],
+ $request,
+ 'note' === $comment->comment_type
+ );
+
+ if ( is_wp_error( $target_check ) ) {
+ return $target_check;
+ }
+ }
+
return true;
}
@@ -2041,6 +2064,106 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
return '' !== $check['comment_content'];
}
+ /**
+ * Checks that a post can receive a comment or a note from the current user.
+ *
+ * Used when creating a note and when changing the parent post of an existing
+ * comment or note, so that attaching content to a post is authorized the same
+ * way whichever path it arrives by.
+ *
+ * @since 7.1.1
+ *
+ * @param int $post_id Target post ID.
+ * @param WP_REST_Request $request Full details about the request.
+ * @param bool $is_note Optional. Whether the comment is a note. Default false.
+ * @return true|WP_Error True if the post can receive the comment, error object otherwise.
+ */
+ protected function check_target_post_permission( int $post_id, WP_REST_Request $request, bool $is_note = false ) {
+ if ( ! $post_id ) {
+ return new WP_Error(
+ 'rest_comment_invalid_post_id',
+ __( 'Sorry, you are not allowed to create this comment without a post.' ),
+ array( 'status' => 403 )
+ );
+ }
+
+ /*
+ * Notes are editorial content, so they may only be attached to a post the
+ * user can edit. Any other comment needs either comment moderation rights
+ * or edit access to the post, which is what check_edit_permission() grants
+ * on the post a comment is moving away from. Requiring the same at the
+ * destination means both ends of a move are authorized alike.
+ */
+ if ( $is_note ) {
+ $can_target_post = current_user_can( 'edit_post', $post_id );
+ } else {
+ $can_target_post = current_user_can( 'moderate_comments' ) || current_user_can( 'edit_post', $post_id );
+ }
+
+ if ( ! $can_target_post ) {
+ if ( $is_note ) {
+ return new WP_Error(
+ 'rest_cannot_create_note',
+ __( 'Sorry, you are not allowed to create notes for this post.' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+
+ return new WP_Error(
+ 'rest_cannot_edit',
+ __( 'Sorry, you are not allowed to edit this comment.' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+
+ $post = get_post( $post_id );
+
+ if ( ! $post ) {
+ return new WP_Error(
+ 'rest_comment_invalid_post_id',
+ __( 'Sorry, you are not allowed to create this comment without a post.' ),
+ array( 'status' => 403 )
+ );
+ }
+
+ /*
+ * The remaining rules mirror the create-time checks for notes only. They are
+ * deliberately not applied to other comments, because moderators move comments
+ * onto posts whose discussion has closed and onto drafts today. Enforcing the
+ * create-time rules there would break that without blocking anything the
+ * capability check above already permits.
+ */
+ if ( ! $is_note ) {
+ return true;
+ }
+
+ if ( ! $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 ( 'trash' === $post->post_status ) {
+ return new WP_Error(
+ 'rest_comment_trash_post',
+ __( 'Sorry, you are not allowed to create a comment on this post.' ),
+ array( 'status' => 403 )
+ );
+ }
+
+ if ( ! $this->check_read_post_permission( $post, $request ) ) {
+ return new WP_Error(
+ 'rest_cannot_read_post',
+ __( 'Sorry, you are not allowed to read the post for this comment.' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+
+ return true;
+ }
+
/**
* Check if post type supports notes.
*
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 2995bd0538..cd8187ac44 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-alpha-63672';
+$wp_version = '7.2-alpha-63675';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.