Commit ba67e4c924e for woocommerce
commit ba67e4c924e4fd149b233b3ade560cfda88d2471
Author: Yuliyan Slavchev <yuliyan.slavchev@gmail.com>
Date: Mon Feb 23 21:06:22 2026 +0200
Email Editor: Check edit permission for post in send preview email (#63413)
* Email Editor: Check edit permission for post in send preview email
* Add changelog
* Add tests
* Fix lint and phpstan
diff --git a/packages/php/email-editor/changelog/wooplug-6338-improve-permission-checks-for-send-preview-email-endpoint b/packages/php/email-editor/changelog/wooplug-6338-improve-permission-checks-for-send-preview-email-endpoint
new file mode 100644
index 00000000000..4ff22ca47bf
--- /dev/null
+++ b/packages/php/email-editor/changelog/wooplug-6338-improve-permission-checks-for-send-preview-email-endpoint
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Verify user can edit post in send preview email endpoint permission check
diff --git a/packages/php/email-editor/src/Engine/class-email-editor.php b/packages/php/email-editor/src/Engine/class-email-editor.php
index 6363a0e448c..3b26a1f95b0 100644
--- a/packages/php/email-editor/src/Engine/class-email-editor.php
+++ b/packages/php/email-editor/src/Engine/class-email-editor.php
@@ -13,6 +13,7 @@ use Automattic\WooCommerce\EmailEditor\Engine\PersonalizationTags\Personalizatio
use Automattic\WooCommerce\EmailEditor\Engine\Templates\Templates;
use Automattic\WooCommerce\EmailEditor\Engine\Logger\Email_Editor_Logger;
use WP_Post;
+use WP_REST_Request;
use WP_Theme_JSON;
/**
@@ -263,8 +264,15 @@ class Email_Editor {
array(
'methods' => 'POST',
'callback' => array( $this->email_api_controller, 'send_preview_email_data' ),
- 'permission_callback' => function () {
- return current_user_can( 'edit_posts' );
+ 'permission_callback' => function ( WP_REST_Request $request ) {
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return false;
+ }
+ $post_id = $request->get_param( 'postId' );
+ if ( ! is_numeric( $post_id ) || (int) $post_id <= 0 ) {
+ return false;
+ }
+ return current_user_can( 'edit_post', (int) $post_id );
},
)
);
diff --git a/packages/php/email-editor/tests/integration/Engine/Send_Preview_Email_Permission_Test.php b/packages/php/email-editor/tests/integration/Engine/Send_Preview_Email_Permission_Test.php
new file mode 100644
index 00000000000..076e101fe03
--- /dev/null
+++ b/packages/php/email-editor/tests/integration/Engine/Send_Preview_Email_Permission_Test.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * This file is part of the WooCommerce Email Editor package
+ *
+ * @package Automattic\WooCommerce\EmailEditor
+ */
+
+declare(strict_types = 1);
+namespace Automattic\WooCommerce\EmailEditor\Engine;
+
+/**
+ * Integration test for the send_preview_email endpoint permission callback.
+ */
+class Send_Preview_Email_Permission_Test extends \Email_Editor_Integration_Test_Case {
+
+ /**
+ * Email editor instance.
+ *
+ * @var Email_Editor
+ */
+ private $email_editor;
+
+ /**
+ * REST server instance.
+ *
+ * @var \WP_REST_Server
+ */
+ private $server;
+
+ /**
+ * The endpoint route.
+ *
+ * @var string
+ */
+ private const ROUTE = '/woocommerce-email-editor/v1/send_preview_email';
+
+ /**
+ * Creates a user and returns an integer ID.
+ *
+ * @param array $args Arguments for user creation.
+ * @return int
+ */
+ private function create_user( array $args ): int {
+ $result = self::factory()->user->create( $args );
+ $this->assertIsInt( $result );
+ return $result;
+ }
+
+ /**
+ * Creates a post and returns an integer ID.
+ *
+ * @param array $args Arguments for post creation.
+ * @return int
+ */
+ private function create_post( array $args = array() ): int {
+ $result = self::factory()->post->create( $args );
+ $this->assertIsInt( $result );
+ return $result;
+ }
+
+ /**
+ * Set up before each test.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ $this->email_editor = $this->di_container->get( Email_Editor::class );
+
+ global $wp_rest_server;
+ $wp_rest_server = new \WP_REST_Server();
+ $this->server = $wp_rest_server;
+
+ do_action( 'rest_api_init' );
+ $this->email_editor->register_email_editor_api_routes();
+ }
+
+ /**
+ * Clean up after each test.
+ */
+ public function tearDown(): void {
+ parent::tearDown();
+ global $wp_rest_server;
+ $wp_rest_server = null;
+ }
+
+ /**
+ * Test that an admin can send a preview email for their own post.
+ */
+ public function testAdminCanSendPreviewForOwnPost(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ $post_id = $this->create_post( array( 'post_author' => $admin_id ) );
+
+ wp_set_current_user( $admin_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => $post_id,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertNotEquals( 403, $response->get_status(), 'Admin should be allowed to send preview for own post' );
+ }
+
+ /**
+ * Test that an editor can send a preview email for another user's post.
+ */
+ public function testEditorCanSendPreviewForOtherUsersPost(): void {
+ $author_id = $this->create_user( array( 'role' => 'author' ) );
+ $editor_id = $this->create_user( array( 'role' => 'editor' ) );
+ $post_id = $this->create_post( array( 'post_author' => $author_id ) );
+
+ wp_set_current_user( $editor_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => $post_id,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertNotEquals( 403, $response->get_status(), 'Editor should be allowed to send preview for another user\'s post' );
+ }
+
+ /**
+ * Test that a contributor cannot send a preview email for another user's private post.
+ */
+ public function testContributorCannotSendPreviewForOtherUsersPrivatePost(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ $contributor_id = $this->create_user( array( 'role' => 'contributor' ) );
+ $post_id = $this->create_post(
+ array(
+ 'post_author' => $admin_id,
+ 'post_status' => 'private',
+ )
+ );
+
+ wp_set_current_user( $contributor_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => $post_id,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 403, $response->get_status(), 'Contributor should not be allowed to send preview for another user\'s private post' );
+ }
+
+ /**
+ * Test that a subscriber cannot send a preview email.
+ */
+ public function testSubscriberCannotSendPreview(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ $subscriber_id = $this->create_user( array( 'role' => 'subscriber' ) );
+ $post_id = $this->create_post( array( 'post_author' => $admin_id ) );
+
+ wp_set_current_user( $subscriber_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => $post_id,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 403, $response->get_status(), 'Subscriber should not be allowed to send preview email' );
+ }
+
+ /**
+ * Test that a request without postId is denied.
+ */
+ public function testRequestWithoutPostIdIsDenied(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $admin_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 403, $response->get_status(), 'Request without postId should be denied' );
+ }
+
+ /**
+ * Test that a request with non-numeric postId is denied.
+ */
+ public function testRequestWithNonNumericPostIdIsDenied(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $admin_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => 'abc',
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 403, $response->get_status(), 'Request with non-numeric postId should be denied' );
+ }
+
+ /**
+ * Test that a request with zero postId is denied.
+ */
+ public function testRequestWithZeroPostIdIsDenied(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $admin_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => 0,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 403, $response->get_status(), 'Request with zero postId should be denied' );
+ }
+
+ /**
+ * Test that a request with negative postId is denied.
+ */
+ public function testRequestWithNegativePostIdIsDenied(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $admin_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => -1,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 403, $response->get_status(), 'Request with negative postId should be denied' );
+ }
+
+ /**
+ * Test that an unauthenticated request is denied.
+ */
+ public function testUnauthenticatedRequestIsDenied(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ $post_id = $this->create_post( array( 'post_author' => $admin_id ) );
+
+ wp_set_current_user( 0 );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => $post_id,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertContains( $response->get_status(), array( 401, 403 ), 'Unauthenticated request should be denied' );
+ }
+
+ /**
+ * Test that a request with non-existent postId is denied.
+ */
+ public function testRequestWithNonExistentPostIdIsDenied(): void {
+ $admin_id = $this->create_user( array( 'role' => 'administrator' ) );
+ wp_set_current_user( $admin_id );
+
+ $request = new \WP_REST_Request( 'POST', self::ROUTE );
+ $request->set_body_params(
+ array(
+ 'email' => 'test@example.com',
+ 'postId' => 999999,
+ )
+ );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 403, $response->get_status(), 'Request with non-existent postId should be denied' );
+ }
+}