Return HTTP 404 in REST API for non-existent product reviews (#48726)

* Return HTTP 404 when accessing non-existent product views via REST API

* Fix unit tests

* Add changelog

* Address feedback
This commit is contained in:
Jorge A. Torres 2024-07-11 16:30:20 +01:00 committed by GitHub
parent ec95afc178
commit 21cc919094
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 84 additions and 58 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Return HTTP 404 when trying to read or delete a non-existent product review.

View File

@ -144,6 +144,11 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$review = $this->get_review( (int) $request['id'], (int) $request['product_id'] );
if ( is_wp_error( $review ) ) {
return $review;
}
if ( ! wc_rest_check_product_reviews_permissions( 'read', (int) $request['id'] ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
@ -172,6 +177,11 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
$review = $this->get_review( (int) $request['id'], (int) $request['product_id'] );
if ( is_wp_error( $review ) ) {
return $review;
}
if ( ! wc_rest_check_product_reviews_permissions( 'edit', (int) $request['id'] ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
@ -186,6 +196,11 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
$review = $this->get_review( (int) $request['id'], (int) $request['product_id'] );
if ( is_wp_error( $review ) ) {
return $review;
}
if ( ! wc_rest_check_product_reviews_permissions( 'delete', (int) $request['id'] ) ) {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you cannot delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
@ -218,6 +233,28 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
return rest_ensure_response( $data );
}
/**
* Fetch a single product review from the database.
*
* @param int $id Review ID.
* @param int $product_id Product ID.
*
* @since 9.2.0
* @return \WP_Comment
*/
protected function get_review( int $id, int $product_id ) {
if ( 0 >= $product_id || 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$review = 0 <= $id ? get_comment( $id ) : null;
if ( empty( $review ) || empty( $review->comment_ID ) || 'review' !== get_comment_type( $id ) || empty( $review->comment_post_ID ) || (int) $review->comment_post_ID !== $product_id ) {
return new WP_Error( 'woocommerce_rest_product_review_invalid_id', __( 'Invalid product review ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
return $review;
}
/**
* Get a single product review.
*
@ -225,17 +262,9 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$id = (int) $request['id'];
$product_id = (int) $request['product_id'];
if ( 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$review = get_comment( $id );
if ( empty( $id ) || empty( $review ) || intval( $review->comment_post_ID ) !== $product_id ) {
return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
$review = $this->get_review( (int) $request['id'], (int) $request['product_id'] );
if ( is_wp_error( $review ) ) {
return $review;
}
$delivery = $this->prepare_item_for_response( $review, $request );
@ -309,14 +338,9 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
$product_review_id = (int) $request['id'];
$product_id = (int) $request['product_id'];
if ( 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$review = get_comment( $product_review_id );
if ( empty( $product_review_id ) || empty( $review ) || intval( $review->comment_post_ID ) !== $product_id ) {
return new WP_Error( 'woocommerce_rest_product_review_invalid_id', __( 'Invalid resource ID.', 'woocommerce' ), array( 'status' => 404 ) );
$review = $this->get_review( $product_review_id, $product_id );
if ( is_wp_error( $review ) ) {
return $review;
}
$prepared_review = $this->prepare_item_for_database( $request );
@ -358,15 +382,11 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
public function delete_item( $request ) {
$product_id = (int) $request['product_id'];
$product_review_id = (int) $request['id'];
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
$product_review = $this->get_review( $product_review_id, $product_id );
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
if ( 'product' !== get_post_type( $product_id ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$product_review = get_comment( $product_review_id );
if ( empty( $product_review_id ) || empty( $product_review->comment_ID ) || empty( $product_review->comment_post_ID ) ) {
return new WP_Error( 'woocommerce_rest_product_review_invalid_id', __( 'Invalid product review ID.', 'woocommerce' ), array( 'status' => 404 ) );
if ( is_wp_error( $product_review ) ) {
return $product_review;
}
/**

View File

@ -149,6 +149,11 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$review = $this->get_review( (int) $request['id'] );
if ( is_wp_error( $review ) ) {
return $review;
}
if ( ! wc_rest_check_product_reviews_permissions( 'read', (int) $request['id'] ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
@ -177,6 +182,11 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
$review = $this->get_review( (int) $request['id'] );
if ( is_wp_error( $review ) ) {
return $review;
}
if ( ! wc_rest_check_product_reviews_permissions( 'edit', (int) $request['id'] ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
@ -191,6 +201,11 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
$review = $this->get_review( (int) $request['id'] );
if ( is_wp_error( $review ) ) {
return $review;
}
if ( ! wc_rest_check_product_reviews_permissions( 'delete', (int) $request['id'] ) ) {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you cannot delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
@ -1057,13 +1072,11 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
}
$review = get_comment( $id );
if ( empty( $review ) ) {
if ( empty( $review ) || 'review' !== get_comment_type( $id ) ) {
return $error;
}
if ( ! empty( $review->comment_post_ID ) ) {
$post = get_post( (int) $review->comment_post_ID );
if ( 'product' !== get_post_type( (int) $review->comment_post_ID ) ) {
return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) );
}

View File

@ -1,6 +1,6 @@
const { test, expect } = require( '@playwright/test' );
const { API_BASE_URL } = process.env;
const shouldSkip = API_BASE_URL != undefined;
const shouldSkip = API_BASE_URL !== undefined;
/**
* Internal dependencies
@ -717,12 +717,7 @@ test.describe( 'Products API tests: CRUD', () => {
const getDeletedProductReviewResponse = await request.get(
`wp-json/wc/v3/products/reviews/${ productReviewId }`
);
/**
* currently returns a 403 (forbidden) rather than a 404 (not found)
* an issue has been raised to track this
* See: https://github.com/woocommerce/woocommerce/issues/35162
*/
expect( getDeletedProductReviewResponse.status() ).toEqual( 403 );
expect( getDeletedProductReviewResponse.status() ).toEqual( 404 );
} );
test( 'can batch update product reviews', async ( { request } ) => {
@ -817,12 +812,7 @@ test.describe( 'Products API tests: CRUD', () => {
const getDeletedProductReviewResponse = await request.get(
`wp-json/wc/v3/products/reviews/${ review2Id }`
);
/**
* currently returns a 403 (forbidden) rather than a 404 (not found)
* an issue has been raised to track this
* See: https://github.com/woocommerce/woocommerce/issues/35162
*/
expect( getDeletedProductReviewResponse.status() ).toEqual( 403 );
expect( getDeletedProductReviewResponse.status() ).toEqual( 404 );
// Batch delete the created tags
await request.post( `wp-json/wc/v3/products/reviews/batch`, {
@ -1534,12 +1524,9 @@ test.describe( 'Products API tests: CRUD', () => {
// Send request to batch delete the products created earlier
const idsToDelete = expectedProducts.map( ( { id } ) => id );
const batchDeletePayload = batch( 'delete', idsToDelete );
const response = await request.post(
'wp-json/wc/v3/products/batch',
{
data: batchDeletePayload,
}
);
let response = await request.post( 'wp-json/wc/v3/products/batch', {
data: batchDeletePayload,
} );
const responseJSON = await response.json();
const actualBatchDeletedProducts = responseJSON.delete;
@ -1556,7 +1543,7 @@ test.describe( 'Products API tests: CRUD', () => {
// Verify that the deleted product ID's can no longer be retrieved
for ( const id of idsToDelete ) {
const response = await request.get(
response = await request.get(
`wp-json/wc/v3/products/${ id }`
);
expect( response.status() ).toEqual( 404 );

View File

@ -75,6 +75,7 @@ class WC_REST_Product_Reviews_V1_Controller_Tests extends WC_Unit_Test_Case {
*/
public function test_permissions_for_updating_product_reviews() {
$api_request = new WP_REST_Request( 'PUT', '/wc/v1/products/' . $this->product_id . '/reviews/' . $this->review_id );
$api_request->set_param( 'product_id', $this->product_id );
$api_request->set_param( 'id', $this->review_id );
$api_request->set_body( '{ "review": "Modified automatically." }' );
@ -93,6 +94,7 @@ class WC_REST_Product_Reviews_V1_Controller_Tests extends WC_Unit_Test_Case {
$nonexistent_product_id = $this->product_id * 10;
$api_request->set_route( "/wc/v1/products/{$nonexistent_product_id}/reviews/" . $this->review_id );
$api_request->set_param( 'product_id', $nonexistent_product_id );
$this->assertEquals(
'woocommerce_rest_product_invalid_id',
@ -146,8 +148,8 @@ class WC_REST_Product_Reviews_V1_Controller_Tests extends WC_Unit_Test_Case {
$request->set_param( 'id', $order_note_id );
$this->assertEquals(
'woocommerce_rest_cannot_delete',
$this->sut->delete_item_permissions_check( $request )->get_error_code(),
'woocommerce_rest_product_invalid_id',
$this->sut->delete_item( $request )->get_error_code(),
'Comments that are not product reviews cannot be deleted via this endpoint.'
);
@ -163,8 +165,8 @@ class WC_REST_Product_Reviews_V1_Controller_Tests extends WC_Unit_Test_Case {
$request->set_param( 'id', $comment_id );
$this->assertEquals(
'woocommerce_rest_cannot_delete',
$this->sut->delete_item_permissions_check( $request )->get_error_code(),
'woocommerce_rest_product_invalid_id',
$this->sut->delete_item( $request )->get_error_code(),
'Comments that are not product reviews (including other types of comments belonging to products) cannot be deleted via this endpoint.'
);
}

View File

@ -178,8 +178,8 @@ class WC_REST_Product_Reviews_Controller_Tests extends WC_REST_Unit_Test_Case {
$request->set_param( 'id', $order_note_id );
$this->assertEquals(
'woocommerce_rest_cannot_delete',
$this->sut->delete_item_permissions_check( $request )->get_error_code(),
'woocommerce_rest_review_invalid_id',
$this->sut->delete_item( $request )->get_error_code(),
'Comments that are not product reviews cannot be deleted via this endpoint.'
);
@ -195,8 +195,8 @@ class WC_REST_Product_Reviews_Controller_Tests extends WC_REST_Unit_Test_Case {
$request->set_param( 'id', $comment_id );
$this->assertEquals(
'woocommerce_rest_cannot_delete',
$this->sut->delete_item_permissions_check( $request )->get_error_code(),
'woocommerce_rest_review_invalid_id',
$this->sut->delete_item( $request )->get_error_code(),
'Comments that are not product reviews (including other types of comments belonging to products) cannot be deleted via this endpoint.'
);
}