Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/yith-woocommerce-wishlist/includes/class-yith-wcwl-ajax-handler.php
+++ b/yith-woocommerce-wishlist/includes/class-yith-wcwl-ajax-handler.php
@@ -302,16 +302,12 @@
);
}
- if ( ! empty( $fragments ) ) {
- foreach ( $fragments as $id => $options ) {
- if ( isset( $options['is_user_owner'] ) && ! $options['is_user_owner'] ) {
- wp_send_json(
- array(
- 'result' => false,
- )
- );
- }
- }
+ if ( $wishlist && ! $wishlist->is_current_user_owner() ) {
+ wp_send_json(
+ array(
+ 'result' => false,
+ )
+ );
}
$wishlist->set_name( $wishlist_name );
--- a/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-controller.php
+++ b/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-controller.php
@@ -8,27 +8,37 @@
if ( ! class_exists( 'YITH_WCWL_Rest_V1_Controller' ) ) {
abstract class YITH_WCWL_Rest_V1_Controller extends WP_REST_Controller {
/**
- * Get item permission
+ * Default permission check for reading a single item.
*
- * TODO: implement this method
+ * Deny by default. Child controllers should override this method
+ * with their own permission logic.
*
- * @param WP_REST_Request $request
+ * @param WP_REST_Request $request The rest request.
* @return true|WP_Error
*/
public function get_item_permissions_check( $request ) {
- return true;
+ return new WP_Error(
+ 'wishlist_rest_forbidden',
+ __( 'Sorry, you are not allowed to access this resource.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 403 )
+ );
}
/**
- * Get item permission
+ * Default permission check for reading multiple items.
*
- * TODO: implement this method
+ * Deny by default. Child controllers should override this method
+ * with their own permission logic.
*
- * @param $request
- * @return true|WP_Error
+ * @param WP_REST_Request $request The rest request.
+ * @return true|WP_Error
*/
public function get_items_permissions_check( $request ) {
- return true;
+ return new WP_Error(
+ 'wishlist_rest_forbidden',
+ __( 'Sorry, you are not allowed to access this resource.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 403 )
+ );
}
/**
--- a/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-items-controller.php
+++ b/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-items-controller.php
@@ -95,7 +95,7 @@
'description' => _x( 'The Wishlist ID', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
'type' => array( 'integer', 'string' ),
),
- 'user_id' => array(
+ 'user_id' => array(
'description' => _x( 'The User ID', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
'type' => 'integer',
),
@@ -112,27 +112,27 @@
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'move_item' ),
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'move_item_permissions_check' ),
'args' => array(
- 'product_id' => array(
+ 'product_id' => array(
'description' => _x( 'The product ID', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
'type' => 'integer',
'required' => true,
),
'destination_wishlist' => array(
'description' => _x( 'The destination wishlist ID', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
- 'type' => array( 'integer', 'string' ),
- 'required' => true,
+ 'type' => array( 'integer', 'string' ),
+ 'required' => true,
),
- 'origin_wishlist' => array(
+ 'origin_wishlist' => array(
'description' => _x( 'The origin wishlist ID', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
- 'type' => 'integer',
+ 'type' => 'integer',
),
- 'wishlist_name' => array(
+ 'wishlist_name' => array(
'description' => _x( 'The wishlist name.', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
'type' => 'string',
),
- 'wishlist_visibility' => array(
+ 'wishlist_visibility' => array(
'description' => _x( 'The wishlist visibility value.', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
'type' => 'integer',
),
@@ -155,28 +155,31 @@
$response = array( 'success' => true );
$creating_default = false;
- if ( ! empty( $args[ 'wishlist_id' ] ) && 'default' === $args[ 'wishlist_id' ] ) {
+ if ( ! empty( $args['wishlist_id'] ) && 'default' === $args['wishlist_id'] ) {
$creating_default = true;
- unset( $args[ 'wishlist_id' ] );
+ unset( $args['wishlist_id'] );
}
try {
$data = YITH_WCWL_Wishlists::get_instance()->add_item( $args );
- $product_id = $args[ 'product_id' ];
+ $product_id = $args['product_id'];
$response = array(
'product_data' => array_merge(
$this->prepare_product_for_rest( $product_id ),
array(
- 'added_to' => $data[ 'wishlist_id' ],
+ 'added_to' => $data['wishlist_id'],
)
),
);
if ( $creating_default ) {
- $response[ 'wishlist_data' ] = $this->prepare_wishlist_for_rest( $data[ 'wishlist_id' ] );
+ $response['wishlist_data'] = $this->prepare_wishlist_for_rest( $data['wishlist_id'] );
}
} catch ( Exception $e ) {
- $response = array( 'success' => false, 'message' => $e->getMessage() );
+ $response = array(
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ );
}
return rest_ensure_response( $response );
@@ -195,15 +198,18 @@
try {
$data = YITH_WCWL_Wishlists::get_instance()->remove_item( $args );
- $product_id = $args[ 'product_id' ];
+ $product_id = $args['product_id'];
$response = array(
'product_data' => array_merge(
- array( 'removed_from' => $data[ 'wishlist_id' ] ),
+ array( 'removed_from' => $data['wishlist_id'] ),
$this->prepare_product_for_rest( $product_id )
),
);
} catch ( Exception $e ) {
- $response = array( 'success' => false, 'message' => $e->getMessage() );
+ $response = array(
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ );
}
return rest_ensure_response( $response );
@@ -222,30 +228,91 @@
try {
$result = YITH_WCWL_Wishlists::get_instance()->move( $args );
$response = array(
- 'moved' => $result[ 'moved' ],
+ 'moved' => $result['moved'],
'product_data' => $this->prepare_product_for_rest(
- $args[ 'product_id' ],
+ $args['product_id'],
array(
- 'moved_from' => $args[ 'origin_wishlist' ],
- 'moved_to' => 'new' === $args[ 'destination_wishlist' ] ? $result[ 'destination_wishlist' ]->get_id() : $args[ 'destination_wishlist' ],
+ 'moved_from' => $args['origin_wishlist'],
+ 'moved_to' => 'new' === $args['destination_wishlist'] ? $result['destination_wishlist']->get_id() : $args['destination_wishlist'],
)
),
);
- if ( $result[ 'destination_wishlist' ] instanceof YITH_WCWL_Wishlist ) {
- $response[ 'destination_wishlist' ] = $this->prepare_wishlist_for_rest( $result[ 'destination_wishlist' ] );
+ if ( $result['destination_wishlist'] instanceof YITH_WCWL_Wishlist ) {
+ $response['destination_wishlist'] = $this->prepare_wishlist_for_rest( $result['destination_wishlist'] );
}
} catch ( Exception $e ) {
- $response = array( 'success' => false, 'message' => $e->getMessage() );
+ $response = array(
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ );
}
return rest_ensure_response( $response );
}
/**
+ * Move item permission checks.
+ *
+ * Verifies the current user can remove from the origin wishlist
+ * and add to the destination wishlist.
+ *
+ * @param WP_REST_Request $request The rest request.
+ * @return true|WP_Error
+ */
+ public function move_item_permissions_check( $request ) {
+ $origin_id = $request->get_param( 'origin_wishlist' );
+ $destination_id = $request->get_param( 'destination_wishlist' );
+
+ // Check permission on origin wishlist.
+ if ( $origin_id ) {
+ try {
+ $origin = new Wishlist( $origin_id );
+
+ if ( ! $origin->current_user_can( 'remove_from_wishlist' ) ) {
+ return new WP_Error(
+ 'wishlist_rest_cannot_move',
+ __( 'Sorry, you are not allowed to remove items from the origin wishlist.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 403 )
+ );
+ }
+ } catch ( Exception $e ) {
+ return new WP_Error(
+ 'wishlist_rest_invalid_origin',
+ __( 'The origin wishlist does not exist.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 404 )
+ );
+ }
+ }
+
+ // Check permission on destination wishlist (skip if creating a new one).
+ if ( $destination_id && 'new' !== $destination_id ) {
+ try {
+ $destination = new Wishlist( $destination_id );
+
+ if ( ! $destination->current_user_can( 'add_to_wishlist' ) ) {
+ return new WP_Error(
+ 'wishlist_rest_cannot_move',
+ __( 'Sorry, you are not allowed to add items to the destination wishlist.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 403 )
+ );
+ }
+ } catch ( Exception $e ) {
+ return new WP_Error(
+ 'wishlist_rest_invalid_destination',
+ __( 'The destination wishlist does not exist.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 404 )
+ );
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Create item permission checks
*
- * @param WP_REST_Request $request
+ * @param WP_REST_Request $request The rest request.
* @return true|WP_Error
*/
public function create_item_permissions_check( $request ) {
@@ -257,7 +324,7 @@
/**
* Delete item permission checks
*
- * @param WP_REST_Request $request
+ * @param WP_REST_Request $request The rest request.
* @return true|WP_Error
*/
public function delete_item_permissions_check( $request ) {
--- a/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-lists-controller.php
+++ b/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-lists-controller.php
@@ -40,7 +40,7 @@
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_lists' ),
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'get_lists_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema(),
),
'schema' => array( $this, 'get_public_item_schema' ),
@@ -53,7 +53,7 @@
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_list' ),
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'create_list_permissions_check' ),
'args' => array(
'wishlist_name' => array(
'description' => _x( 'The wishlist name.', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
@@ -94,6 +94,32 @@
return rest_ensure_response( $response );
}
+ /**
+ * Check if the current user can view wishlists.
+ *
+ * Allows access only if the user is logged in or has an active session.
+ *
+ * @param WP_REST_Request $request The rest request.
+ * @return true|WP_Error
+ */
+ public function get_lists_permissions_check( $request ) {
+ if ( ! is_user_logged_in() && ! YITH_WCWL_Session()->maybe_get_session_id() ) {
+ return new WP_Error(
+ 'wishlist_rest_cannot_view',
+ __( 'Sorry, you must be logged in or have an active session to view wishlists.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 401 )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Create list
+ *
+ * @param WP_REST_Request $request The rest request.
+ * @return WP_Error|WP_HTTP_Response|WP_REST_Response
+ */
public function create_list( $request ) {
$args = $request->get_params();
@@ -111,5 +137,38 @@
return rest_ensure_response( $response );
}
+
+ /**
+ * Check if the current user can create a wishlist.
+ *
+ * Prevents unauthenticated users from creating wishlists for arbitrary user IDs.
+ *
+ * @param WP_REST_Request $request The rest request.
+ * @return true|WP_Error
+ */
+ public function create_list_permissions_check( $request ) {
+ $user_id = $request->get_param( 'user_id' );
+
+ // If a user_id is provided, only allow if the current user matches or is an admin.
+ if ( $user_id ) {
+ if ( ! is_user_logged_in() ) {
+ return new WP_Error(
+ 'wishlist_rest_cannot_create',
+ __( 'Sorry, you must be logged in to create a wishlist for a user.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 401 )
+ );
+ }
+
+ if ( (int) get_current_user_id() !== (int) $user_id && ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown
+ return new WP_Error(
+ 'wishlist_rest_cannot_create',
+ __( 'Sorry, you are not allowed to create wishlists for other users.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 403 )
+ );
+ }
+ }
+
+ return true;
+ }
}
}
--- a/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-products-controller.php
+++ b/yith-woocommerce-wishlist/includes/rest-api/controllers/v1/class-yith-wcwl-rest-v1-products-controller.php
@@ -33,21 +33,25 @@
* Register the routes.
*/
public function register_routes() {
- register_rest_route( $this->namespace, '/' . $this->rest_base, array(
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'get_products_data' ),
- 'permission_callback' => '__return_true', // TODO: implement checks for permissions.
- 'args' => array(
- 'product_ids' => array(
- 'description' => _x( 'The list of product ids for which data is being requested.', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
- 'type' => 'array',
- 'required' => true,
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_products_data' ),
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
+ 'args' => array(
+ 'product_ids' => array(
+ 'description' => _x( 'The list of product ids for which data is being requested.', '[REST-API] The schema field description', 'yith-woocommerce-wishlist' ),
+ 'type' => 'array',
+ 'required' => true,
+ ),
),
),
- ),
- 'schema' => array( $this, 'get_public_item_schema' ),
- ) );
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
register_rest_route(
$this->namespace,
@@ -63,7 +67,7 @@
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_product_data' ),
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema(),
),
'schema' => array( $this, 'get_public_item_schema' ),
@@ -83,8 +87,8 @@
$products_data = array();
- if ( ! empty( $args[ 'product_ids' ] ) ) {
- $products_data = $this->prepare_products_for_rest( $args[ 'product_ids' ] );
+ if ( ! empty( $args['product_ids'] ) ) {
+ $products_data = $this->prepare_products_for_rest( $args['product_ids'] );
}
return rest_ensure_response( $products_data );
@@ -100,11 +104,72 @@
public function get_product_data( $request ) {
$args = $request->get_params();
- $product_id = $args[ 'product_id' ];
+ $product_id = $args['product_id'];
- $response = $this->prepare_product_for_rest($product_id);
+ $response = $this->prepare_product_for_rest( $product_id );
return rest_ensure_response( $response );
}
+
+ /**
+ * Check if a given request has access to read a single product.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_item_permissions_check( $request ) {
+ $product_id = absint( $request->get_param( 'product_id' ) );
+ $product = wc_get_product( $product_id );
+
+ if ( ! $product || 'publish' !== $product->get_status() ) {
+ return new WP_Error(
+ 'woocommerce_rest_cannot_view',
+ sprintf(
+ /* translators: %d: product ID */
+ __( 'Sorry, the product #%d is not accessible.', 'yith-woocommerce-wishlist' ),
+ $product_id
+ ),
+ array( 'status' => 403 )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a given request has access to read items.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_items_permissions_check( $request ) {
+ $product_ids = $request->get_param( 'product_ids' );
+
+ if ( empty( $product_ids ) || ! is_array( $product_ids ) ) {
+ return new WP_Error(
+ 'woocommerce_rest_missing_product_ids',
+ __( 'Please provide a valid list of product IDs.', 'yith-woocommerce-wishlist' ),
+ array( 'status' => 400 )
+ );
+ }
+
+ foreach ( $product_ids as $product_id ) {
+ $product = wc_get_product( absint( $product_id ) );
+
+ if ( ! $product || 'publish' !== $product->get_status() ) {
+ return new WP_Error(
+ 'woocommerce_rest_cannot_view',
+ sprintf(
+ /* translators: %d: product ID */
+ __( 'Sorry, the product #%d is not accessible.', 'yith-woocommerce-wishlist' ),
+ absint( $product_id )
+ ),
+ array( 'status' => 403 )
+ );
+ }
+ }
+
+ return true;
+ }
}
}
--- a/yith-woocommerce-wishlist/init.php
+++ b/yith-woocommerce-wishlist/init.php
@@ -3,24 +3,24 @@
* Plugin Name: YITH WooCommerce Wishlist
* Plugin URI: https://yithemes.com/themes/plugins/yith-woocommerce-wishlist/
* Description: <code><strong>YITH WooCommerce Wishlist</strong></code> gives your users the possibility to create, fill, manage and share their wishlists allowing you to analyze their interests and needs to improve your marketing strategies. <a href="https://yithemes.com/" target="_blank">Get more plugins for your e-commerce on <strong>YITH</strong></a>
- * Version: 4.12.0
+ * Version: 4.13.0
* Author: YITH
* Author URI: https://yithemes.com/
* Text Domain: yith-woocommerce-wishlist
* Domain Path: /languages/
- * WC requires at least: 10.3
- * WC tested up to: 10.5
+ * WC requires at least: 10.4
+ * WC tested up to: 10.6
* Requires Plugins: woocommerce
*
* @package YITHWishlist
* @author YITH <plugins@yithemes.com>
- * @version 4.12.0
+ * @version 4.13.0
*/
defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
defined( 'YITH_WCWL' ) || define( 'YITH_WCWL', true );
-defined( 'YITH_WCWL_VERSION' ) || define( 'YITH_WCWL_VERSION', '4.12.0' );
+defined( 'YITH_WCWL_VERSION' ) || define( 'YITH_WCWL_VERSION', '4.13.0' );
defined( 'YITH_WCWL_URL' ) || define( 'YITH_WCWL_URL', plugin_dir_url( __FILE__ ) );
defined( 'YITH_WCWL_DIR' ) || define( 'YITH_WCWL_DIR', plugin_dir_path( __FILE__ ) );
defined( 'YITH_WCWL_INC' ) || define( 'YITH_WCWL_INC', YITH_WCWL_DIR . 'includes/' );