From 71a32c96b64b0f9a901c97c7613f7eb3789a6065 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jun 2019 16:06:04 +0100 Subject: [PATCH] Tidy up permission checks --- .../Version4/AbstractController.php | 58 ++-- .../Version4/AbstractObjectsController.php | 148 ++++------ .../Version4/AbstractTermsContoller.php | 84 ------ .../Version4/CustomerDownloads.php | 2 +- src/Controllers/Version4/Customers.php | 64 +++++ src/Controllers/Version4/OrderNotes.php | 33 +-- src/Controllers/Version4/ProductReviews.php | 53 +++- .../Version4/ProductVariations.php | 62 ++++- src/Controllers/Version4/Products.php | 2 +- .../Version4/Utilities/Permissions.php | 260 +++++++++++++----- unit-tests/Tests/Version4/Customers.php | 2 +- 11 files changed, 467 insertions(+), 301 deletions(-) diff --git a/src/Controllers/Version4/AbstractController.php b/src/Controllers/Version4/AbstractController.php index ff2be035ec4..eedc9116f41 100644 --- a/src/Controllers/Version4/AbstractController.php +++ b/src/Controllers/Version4/AbstractController.php @@ -159,6 +159,8 @@ abstract class AbstractController extends WP_REST_Controller { return $schema; } + + /** * Check whether a given request has permission to read webhooks. * @@ -166,10 +168,13 @@ abstract class AbstractController extends WP_REST_Controller { * @return \WP_Error|boolean */ public function get_items_permissions_check( $request ) { - if ( ! Permissions::check_resource( $this->resource_type, 'read' ) ) { + $permission = Permissions::user_can_list( $this->get_item_title() ); + + if ( false === $permission ) { return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } - return true; + + return $permission; } /** @@ -180,10 +185,13 @@ abstract class AbstractController extends WP_REST_Controller { * @return bool|\WP_Error */ public function create_item_permissions_check( $request ) { - if ( ! Permissions::check_resource( $this->resource_type, 'create' ) ) { + $permission = Permissions::user_can_create( $this->get_item_title() ); + + if ( false === $permission ) { return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } - return true; + + return $permission; } /** @@ -193,12 +201,14 @@ abstract class AbstractController extends WP_REST_Controller { * @return \WP_Error|boolean */ public function get_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); + $id = $request->get_param( 'id' ); + $permission = Permissions::user_can_read( $this->get_item_title(), $id ); - if ( 0 !== $id && ! Permissions::check_resource( $this->resource_type, 'read', $id ) ) { + if ( false === $permission ) { return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } - return true; + + return $permission; } /** @@ -209,12 +219,14 @@ abstract class AbstractController extends WP_REST_Controller { * @return bool|\WP_Error */ public function update_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); + $id = $request->get_param( 'id' ); + $permission = Permissions::user_can_edit( $this->get_item_title(), $id ); - if ( 0 !== $id && ! Permissions::check_resource( $this->resource_type, 'edit', $id ) ) { + if ( false === $permission ) { return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } - return true; + + return $permission; } /** @@ -225,12 +237,14 @@ abstract class AbstractController extends WP_REST_Controller { * @return bool|\WP_Error */ public function delete_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); + $id = $request->get_param( 'id' ); + $permission = Permissions::user_can_delete( $this->get_item_title(), $id ); - if ( 0 !== $id && ! Permissions::check_resource( $this->resource_type, 'delete', $id ) ) { + if ( false === $permission ) { return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } - return true; + + return $permission; } /** @@ -241,10 +255,13 @@ abstract class AbstractController extends WP_REST_Controller { * @return bool|\WP_Error */ public function batch_items_permissions_check( $request ) { - if ( ! Permissions::check_resource( $this->resource_type, 'batch' ) ) { + $permission = Permissions::user_can_batch( $this->get_item_title() ); + + if ( false === $permission ) { return new \WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } - return true; + + return $permission; } /** @@ -293,11 +310,20 @@ abstract class AbstractController extends WP_REST_Controller { * * @return string */ - protected function get_hook_suffix() { + protected function get_item_title() { $schema = $this->get_item_schema(); return $schema['title']; } + /** + * Return suffix for item action hooks. + * + * @return string + */ + protected function get_hook_suffix() { + return $this->get_item_title(); + } + /** * Get data for this object in the format of this endpoint's schema. * diff --git a/src/Controllers/Version4/AbstractObjectsController.php b/src/Controllers/Version4/AbstractObjectsController.php index c5a723ba5b8..fb0c1ec10b2 100644 --- a/src/Controllers/Version4/AbstractObjectsController.php +++ b/src/Controllers/Version4/AbstractObjectsController.php @@ -57,97 +57,6 @@ abstract class AbstractObjectsController extends AbstractController { $this->register_batch_route(); } - /** - * 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 ) { - if ( ! Permissions::check_post_object( $this->post_type, 'read' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Check if a given request has access to read an item. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function get_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - - if ( 0 !== $id && ! Permissions::check_post_object( $this->post_type, 'read', $id ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Check if a given request has access to create an item. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function create_item_permissions_check( $request ) { - if ( ! Permissions::check_post_object( $this->post_type, 'create' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Check if a given request has access to update an item. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function update_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - - if ( 0 !== $id && ! Permissions::check_post_object( $this->post_type, 'edit', $id ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Check if a given request has access to delete an item. - * - * @param \WP_REST_Request $request Full details about the request. - * @return bool|\WP_Error - */ - public function delete_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - - if ( 0 !== $id && ! Permissions::check_post_object( $this->post_type, 'delete', $id ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Check if a given request has access batch create, update and delete items. - * - * @param \WP_REST_Request $request Full details about the request. - * - * @return boolean|\WP_Error - */ - public function batch_items_permissions_check( $request ) { - if ( ! Permissions::check_post_object( $this->post_type, 'batch' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - /** * Get a single item. * @@ -379,7 +288,7 @@ abstract class AbstractObjectsController extends AbstractController { $objects = array(); foreach ( $query_results['objects'] as $object ) { - if ( ! Permissions::check_post_object( $this->post_type, 'read', $object->get_id() ) ) { + if ( ! Permissions::user_can_read( $this->post_type, $object->get_id() ) ) { continue; } @@ -444,7 +353,7 @@ abstract class AbstractObjectsController extends AbstractController { $supports_trash = $this->supports_trash( $object ); - if ( ! Permissions::check_post_object( $this->post_type, 'delete', $object->get_id() ) ) { + if ( ! Permissions::user_can_delete( $this->post_type, $object->get_id() ) ) { /* translators: %s: post type */ return new \WP_Error( "woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array( 'status' => rest_authorization_required_code() ) ); } @@ -832,4 +741,57 @@ abstract class AbstractObjectsController extends AbstractController { protected function get_hook_suffix() { return $this->post_type . '_object'; } + + /** + * Check if a given request has access to read a webhook. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function get_item_permissions_check( $request ) { + $id = $request->get_param( 'id' ); + $object = $this->get_object( $id ); + + if ( ! $object || 0 === $object->get_id() ) { + return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); + } + + return parent::get_item_permissions_check( $request ); + } + + /** + * Check if a given request has access update a webhook. + * + * @param \WP_REST_Request $request Full details about the request. + * + * @return bool|\WP_Error + */ + public function update_item_permissions_check( $request ) { + $id = $request->get_param( 'id' ); + $object = $this->get_object( $id ); + + if ( ! $object || 0 === $object->get_id() ) { + return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); + } + + return parent::update_item_permissions_check( $request ); + } + + /** + * Check if a given request has access delete a webhook. + * + * @param \WP_REST_Request $request Full details about the request. + * + * @return bool|\WP_Error + */ + public function delete_item_permissions_check( $request ) { + $id = $request->get_param( 'id' ); + $object = $this->get_object( $id ); + + if ( ! $object || 0 === $object->get_id() ) { + return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); + } + + return parent::delete_item_permissions_check( $request ); + } } diff --git a/src/Controllers/Version4/AbstractTermsContoller.php b/src/Controllers/Version4/AbstractTermsContoller.php index 42169be5682..ffcf6870ba4 100644 --- a/src/Controllers/Version4/AbstractTermsContoller.php +++ b/src/Controllers/Version4/AbstractTermsContoller.php @@ -122,90 +122,6 @@ abstract class AbstractTermsContoller extends AbstractController { $this->register_batch_route(); } - /** - * Check if a given request has access to read the terms. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function get_items_permissions_check( $request ) { - if ( ! Permissions::check_taxonomy( $this->taxonomy, 'read' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - return true; - } - - /** - * Check if a given request has access to create a term. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function create_item_permissions_check( $request ) { - if ( ! Permissions::check_taxonomy( $this->taxonomy, 'create' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - return true; - } - - /** - * Check if a given request has access to read a term. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function get_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - - if ( ! Permissions::check_taxonomy( $this->taxonomy, 'read', $id ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you are not allowed to view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - return true; - } - - /** - * Check if a given request has access to update a term. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function update_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - - if ( ! Permissions::check_taxonomy( $this->taxonomy, 'edit', $id ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - return true; - } - - /** - * Check if a given request has access to delete a term. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function delete_item_permissions_check( $request ) { - $id = $request->get_param( 'id' ); - - if ( ! Permissions::check_taxonomy( $this->taxonomy, 'delete', $id ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - return true; - } - - /** - * Check if a given request has access batch create, update and delete items. - * - * @param \WP_REST_Request $request Full details about the request. - * @return boolean|\WP_Error - */ - public function batch_items_permissions_check( $request ) { - if ( ! Permissions::check_taxonomy( $this->taxonomy, 'batch' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - return true; - } - /** * Get terms associated with a taxonomy. * diff --git a/src/Controllers/Version4/CustomerDownloads.php b/src/Controllers/Version4/CustomerDownloads.php index 342d5fdbba7..18a7cbea7c5 100644 --- a/src/Controllers/Version4/CustomerDownloads.php +++ b/src/Controllers/Version4/CustomerDownloads.php @@ -52,7 +52,7 @@ class CustomerDownloads extends AbstractController { return new \WP_Error( 'woocommerce_rest_customer_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) ); } - if ( ! Permissions::user_can( $this->resource_type, 'read', $customer->ID ) ) { + if ( ! Permissions::user_can_read( $this->resource_type, $customer->ID ) ) { return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } diff --git a/src/Controllers/Version4/Customers.php b/src/Controllers/Version4/Customers.php index 8d6796053fa..84c84bae043 100644 --- a/src/Controllers/Version4/Customers.php +++ b/src/Controllers/Version4/Customers.php @@ -740,4 +740,68 @@ class Customers extends AbstractController { ); return $params; } + + /** + * Check if a given ID is valid. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + protected function check_valid_customer_id( $request ) { + $id = $request->get_param( 'id' ); + $user = get_userdata( $id ); + + if ( false === $user ) { + return new \WP_Error( 'woocommerce_rest_customer_invalid_id', __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); + } + return true; + } + + /** + * Check if a given request has access to read a webhook. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function get_item_permissions_check( $request ) { + $check_valid = $this->check_valid_customer_id( $request ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::get_item_permissions_check( $request ); + } + + /** + * Check if a given request has access to delete an item. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function delete_item_permissions_check( $request ) { + $check_valid = $this->check_valid_customer_id( $request ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::delete_item_permissions_check( $request ); + } + + /** + * Check if a given request has access to update an item. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function update_item_permissions_check( $request ) { + $check_valid = $this->check_valid_customer_id( $request ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::update_item_permissions_check( $request ); + } } diff --git a/src/Controllers/Version4/OrderNotes.php b/src/Controllers/Version4/OrderNotes.php index 01a96b214b4..551ee43c02b 100644 --- a/src/Controllers/Version4/OrderNotes.php +++ b/src/Controllers/Version4/OrderNotes.php @@ -112,35 +112,6 @@ class OrderNotes extends AbstractController { ); } - /** - * Check whether a given request has permission to read order notes. - * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_Error|boolean - */ - public function get_items_permissions_check( $request ) { - if ( ! Permissions::check_post_object( $this->post_type, 'read' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - - /** - * Check if a given request has access create order notes. - * - * @param \WP_REST_Request $request Full details about the request. - * - * @return bool|\WP_Error - */ - public function create_item_permissions_check( $request ) { - if ( ! Permissions::check_post_object( $this->post_type, 'create' ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); - } - - return true; - } - /** * Check if a given request has access to read a order note. * @@ -150,7 +121,7 @@ class OrderNotes extends AbstractController { public function get_item_permissions_check( $request ) { $order = wc_get_order( (int) $request['order_id'] ); - if ( $order && ! Permissions::check_post_object( $this->post_type, 'read', $order->get_id() ) ) { + if ( $order && ! Permissions::user_can_read( $this->post_type, $order->get_id() ) ) { return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } @@ -167,7 +138,7 @@ class OrderNotes extends AbstractController { public function delete_item_permissions_check( $request ) { $order = wc_get_order( (int) $request['order_id'] ); - if ( $order && ! Permissions::check_post_object( $this->post_type, 'delete', $order->get_id() ) ) { + if ( $order && ! Permissions::user_can_delete( $this->post_type, $order->get_id() ) ) { return new \WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } diff --git a/src/Controllers/Version4/ProductReviews.php b/src/Controllers/Version4/ProductReviews.php index 071c9b240e5..35c1d5b27ba 100644 --- a/src/Controllers/Version4/ProductReviews.php +++ b/src/Controllers/Version4/ProductReviews.php @@ -219,7 +219,7 @@ class ProductReviews extends AbstractController { $reviews = array(); foreach ( $query_result as $review ) { - if ( ! Permissions::check_resource( $this->resource_type, 'read', $review->comment_ID ) ) { + if ( ! Permissions::user_can_read( 'product_review', $review->comment_ID ) ) { continue; } @@ -983,4 +983,55 @@ class ProductReviews extends AbstractController { return $changed; } + + /** + * Check if a given request has access to read a webhook. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function get_item_permissions_check( $request ) { + $id = $request->get_param( 'id' ); + $check_valid = $this->get_review( $id ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::get_item_permissions_check( $request ); + } + + /** + * Check if a given request has access to delete an item. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function delete_item_permissions_check( $request ) { + $id = $request->get_param( 'id' ); + $check_valid = $this->get_review( $id ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::delete_item_permissions_check( $request ); + } + + /** + * Check if a given request has access to update an item. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function update_item_permissions_check( $request ) { + $id = $request->get_param( 'id' ); + $check_valid = $this->get_review( $id ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::update_item_permissions_check( $request ); + } } diff --git a/src/Controllers/Version4/ProductVariations.php b/src/Controllers/Version4/ProductVariations.php index b791cbda69b..8243fdc973d 100644 --- a/src/Controllers/Version4/ProductVariations.php +++ b/src/Controllers/Version4/ProductVariations.php @@ -561,26 +561,74 @@ class ProductVariations extends Products { } /** - * Check if a given request has access to update an item. + * Check if a given ID is valid. * * @param \WP_REST_Request $request Full details about the request. * @return \WP_Error|boolean */ - public function update_item_permissions_check( $request ) { - $object = $this->get_object( (int) $request['id'] ); + protected function check_valid_variation_id( $request ) { + $id = $request->get_param( 'id' ); + $object = $this->get_object( $id ); - if ( $object && 0 !== $object->get_id() && ! Permissions::check_post_object( $this->post_type, 'edit', $object->get_id() ) ) { - return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); + if ( ! $object || 0 === $object->get_id() ) { + return new \WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); } // Check if variation belongs to the correct parent product. if ( $object && 0 !== $object->get_parent_id() && absint( $request['product_id'] ) !== $object->get_parent_id() ) { return new \WP_Error( 'woocommerce_rest_cannot_edit', __( 'Parent product does not match current variation.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } - return true; } + /** + * Check if a given request has access to read a webhook. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function get_item_permissions_check( $request ) { + $check_valid = $this->check_valid_variation_id( $request ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::get_item_permissions_check( $request ); + } + + /** + * Check if a given request has access to delete an item. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function delete_item_permissions_check( $request ) { + $check_valid = $this->check_valid_variation_id( $request ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::delete_item_permissions_check( $request ); + } + + /** + * Check if a given request has access to update an item. + * + * @param \WP_REST_Request $request Full details about the request. + * @return \WP_Error|boolean + */ + public function update_item_permissions_check( $request ) { + $check_valid = $this->check_valid_variation_id( $request ); + + if ( is_wp_error( $check_valid ) ) { + return $check_valid; + } + + return parent::update_item_permissions_check( $request ); + } + /** * Get data for this object in the format of this endpoint's schema. * @@ -721,7 +769,7 @@ class ProductVariations extends Products { */ $supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object ); - if ( ! Permissions::check_post_object( $this->post_type, 'delete', $object->get_id() ) ) { + if ( ! Permissions::user_can_delete( $this->post_type, $object->get_id() ) ) { return new \WP_Error( /* translators: %s: post type */ "woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array( diff --git a/src/Controllers/Version4/Products.php b/src/Controllers/Version4/Products.php index cfe86fd63cd..c6e20340e88 100644 --- a/src/Controllers/Version4/Products.php +++ b/src/Controllers/Version4/Products.php @@ -1142,7 +1142,7 @@ class Products extends AbstractObjectsController { */ $supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object ); - if ( ! Permissions::check_post_object( $this->post_type, 'delete', $object->get_id() ) ) { + if ( ! Permissions::user_can_delete( $this->post_type, $object->get_id() ) ) { return new \WP_Error( "woocommerce_rest_user_cannot_delete_{$this->post_type}", /* translators: %s: post type */ diff --git a/src/Controllers/Version4/Utilities/Permissions.php b/src/Controllers/Version4/Utilities/Permissions.php index c70c93573d0..eaa19400b78 100644 --- a/src/Controllers/Version4/Utilities/Permissions.php +++ b/src/Controllers/Version4/Utilities/Permissions.php @@ -17,94 +17,222 @@ defined( 'ABSPATH' ) || exit; class Permissions { /** - * Resource permissions required. + * Items not defined here will default to manage_woocommerce permission. * * @var array */ - protected static $resource_permissions = [ - 'settings' => 'manage_woocommerce', - 'system_status' => 'manage_woocommerce', - 'attributes' => 'manage_product_terms', - 'shipping_methods' => 'manage_woocommerce', - 'payment_gateways' => 'manage_woocommerce', - 'webhooks' => 'manage_woocommerce', - 'product_reviews' => 'moderate_comments', - 'customers' => [ + protected static $capabilities = array( + 'shop_coupon' => [ + 'read' => 'read_shop_coupon', + 'list' => 'read_private_shop_coupons', + 'create' => 'publish_shop_coupons', + 'edit' => 'edit_shop_coupon', + 'delete' => 'delete_shop_coupon', + 'batch' => 'edit_others_shop_coupons', + ], + 'customer_download' => [ + 'read' => 'read_shop_order', + 'list' => 'read_private_shop_orders', + 'create' => 'publish_shop_orders', + 'edit' => 'edit_shop_order', + 'delete' => 'delete_shop_order', + 'batch' => 'edit_others_shop_orders', + ], + 'customer' => [ 'read' => 'list_users', - 'create' => 'promote_users', // Check if current user can create users, shop managers are not allowed to create users. + 'list' => 'list_users', + 'create' => 'promote_users', 'edit' => 'edit_users', 'delete' => 'delete_users', 'batch' => 'promote_users', ], - ]; + 'shop_order' => [ + 'read' => 'read_shop_order', + 'list' => 'read_private_shop_orders', + 'create' => 'publish_shop_orders', + 'edit' => 'edit_shop_order', + 'delete' => 'delete_shop_order', + 'batch' => 'edit_others_shop_orders', + ], + 'product_attribute' => 'edit_product_terms', + 'product_attribute_term' => [ + 'read' => 'manage_product_terms', + 'list' => 'manage_product_terms', + 'create' => 'edit_product_terms', + 'edit' => 'edit_product_terms', + 'delete' => 'delete_product_terms', + 'batch' => 'edit_product_terms', + ], + 'product_cat' => [ + 'read' => 'manage_product_terms', + 'list' => 'manage_product_terms', + 'create' => 'edit_product_terms', + 'edit' => 'edit_product_terms', + 'delete' => 'delete_product_terms', + 'batch' => 'edit_product_terms', + ], + 'product_review' => 'moderate_comments', + 'product' => [ + 'read' => 'read_product', + 'list' => 'read_private_products', + 'create' => 'publish_products', + 'edit' => 'edit_product', + 'delete' => 'delete_product', + 'batch' => 'edit_others_products', + ], + 'product_shipping_class' => [ + 'read' => 'manage_product_terms', + 'list' => 'manage_product_terms', + 'create' => 'edit_product_terms', + 'edit' => 'edit_product_terms', + 'delete' => 'delete_product_terms', + 'batch' => 'edit_product_terms', + ], + 'product_tag' => [ + 'read' => 'manage_product_terms', + 'list' => 'manage_product_terms', + 'create' => 'edit_product_terms', + 'edit' => 'edit_product_terms', + 'delete' => 'delete_product_terms', + 'batch' => 'edit_product_terms', + ], + 'product_variation' => [ + 'read' => 'read_product', + 'list' => 'read_private_products', + 'create' => 'publish_products', + 'edit' => 'edit_product', + 'delete' => 'delete_product', + 'batch' => 'edit_others_products', + ], + ); /** - * See if the current user can do something to a resource. + * Get capabilities required for a resource for a given context. * - * @param string $resource Type of permission required. - * @param string $context Context. One of read, edit, create, update, delete, batch. - * @param int $resource_id Optional resource ID. + * @param string $type Item/resource type. Comes from schema title. + * @param string $context Read, edit, delete, batch, create. + * @return array List of caps to check. Defaults to manage_woocommerce. + */ + protected static function get_capabilities_for_type( $type, $context = 'read' ) { + if ( isset( self::$capabilities[ $type ][ $context ] ) ) { + $caps = self::$capabilities[ $type ][ $context ]; + } elseif ( isset( self::$capabilities[ $type ] ) ) { + $caps = self::$capabilities[ $type ]; + } else { + $caps = 'manage_woocommerce'; + } + return is_array( $caps ) ? $caps : array( $caps ); + } + + /** + * Check if user has a list of caps. + * + * @param array $capabilities List of caps to check. + * @param int $object_id Object ID to check. Optional. * @return boolean */ - public static function check_resource( $resource, $context = 'read', $resource_id = 0 ) { - if ( ! isset( self::$resource_permissions[ $resource ] ) ) { + protected static function has_required_capabilities( $capabilities, $object_id = null ) { + $permission = true; + + foreach ( $capabilities as $capability ) { + if ( ! current_user_can( $capability, $object_id ) ) { + $permission = false; + } + } + + return $permission; + } + + /** + * Check if user can list a collection of resources. + * + * @param string $type Item/resource type. Comes from schema title. + * @return bool True on success. + */ + public static function user_can_list( $type ) { + $capabilities = self::get_capabilities_for_type( $type, 'list' ); + $permission = self::has_required_capabilities( $capabilities ); + + return apply_filters( 'woocommerce_rest_user_can_list', $permission, $type ); + } + + /** + * Check if user can read a resource. + * + * @param string $type Item/resource type. Comes from schema title. + * @param int $object_id Resource ID. 0 to check access to read all. + * @return bool True on success. + */ + public static function user_can_read( $type, $object_id = 0 ) { + if ( 0 === $object_id ) { return false; } - $permissions = self::$resource_permissions[ $resource ]; - $capability = is_array( $permissions ) ? $permissions[ $context ] : $permissions; - $permission = current_user_can( $capability ); - return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $resource_id, $resource ); + $capabilities = self::get_capabilities_for_type( $type, 'read' ); + $permission = self::has_required_capabilities( $capabilities, $object_id ); + + return apply_filters( 'woocommerce_rest_user_can_read', $permission, $type, $object_id ); } /** - * See if the current user can do something to a resource. + * Check if user can read a resource. * - * @param string $taxonomy Taxonomy name. - * @param string $context Context. One of read, edit, create, update, delete, batch. - * @param int $object_id Optional object ID. - * @return boolean + * @param string $type Item/resource type. Comes from schema title. + * @param int $object_id Resource ID. + * @return bool True on success. */ - public static function check_taxonomy( $taxonomy, $context = 'read', $object_id = 0 ) { - $contexts = array( - 'read' => 'manage_terms', - 'create' => 'edit_terms', - 'edit' => 'edit_terms', - 'delete' => 'delete_terms', - 'batch' => 'edit_terms', - ); - $cap = $contexts[ $context ]; - $taxonomy_object = get_taxonomy( $taxonomy ); - $permission = current_user_can( $taxonomy_object->cap->$cap, $object_id ); - - return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, $taxonomy ); - } - - /** - * Check permissions of posts on REST API. - * - * @param string $post_type Post type. - * @param string $context Request context. - * @param int $object_id Post ID. - * @return bool - */ - public static function check_post_object( $post_type, $context = 'read', $object_id = 0 ) { - $contexts = array( - 'read' => 'read_private_posts', - 'create' => 'publish_posts', - 'edit' => 'edit_post', - 'delete' => 'delete_post', - 'batch' => 'edit_others_posts', - ); - - if ( 'revision' === $post_type ) { - $permission = false; - } else { - $cap = $contexts[ $context ]; - $post_type_object = get_post_type_object( $post_type ); - $permission = current_user_can( $post_type_object->cap->$cap, $object_id ); + public static function user_can_edit( $type, $object_id ) { + if ( 0 === $object_id ) { + return false; } - return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, $post_type ); + $capabilities = self::get_capabilities_for_type( $type, 'edit' ); + $permission = self::has_required_capabilities( $capabilities, $object_id ); + + return apply_filters( 'woocommerce_rest_user_can_edit', $permission, $type, $object_id ); + } + + /** + * Check if user can create a resource. + * + * @param string $type Item/resource type. Comes from schema title. + * @return bool True on success. + */ + public static function user_can_create( $type ) { + $capabilities = self::get_capabilities_for_type( $type, 'create' ); + $permission = self::has_required_capabilities( $capabilities ); + + return apply_filters( 'woocommerce_rest_user_can_create', $permission, $type ); + } + + /** + * Check if user can delete a resource. + * + * @param string $type Item/resource type. Comes from schema title. + * @param int $object_id Resource ID. + * @return bool True on success. + */ + public static function user_can_delete( $type, $object_id ) { + if ( 0 === $object_id ) { + return false; + } + + $capabilities = self::get_capabilities_for_type( $type, 'delete' ); + $permission = self::has_required_capabilities( $capabilities, $object_id ); + + return apply_filters( 'woocommerce_rest_user_can_delete', $permission, $type, $object_id ); + } + + /** + * Check if user can batch update a resource. + * + * @param string $type Item/resource type. Comes from schema title. + * @return bool True on success. + */ + public static function user_can_batch( $type ) { + $capabilities = self::get_capabilities_for_type( $type, 'batch' ); + $permission = self::has_required_capabilities( $capabilities ); + + return apply_filters( 'woocommerce_rest_user_can_batch', $permission, $type ); } } diff --git a/unit-tests/Tests/Version4/Customers.php b/unit-tests/Tests/Version4/Customers.php index 0cb812dfc7b..842039d8ecf 100644 --- a/unit-tests/Tests/Version4/Customers.php +++ b/unit-tests/Tests/Version4/Customers.php @@ -533,7 +533,7 @@ class Customers extends WC_REST_Unit_Test_Case { $request = new WP_REST_Request( 'DELETE', '/wc/v4/customers/0' ); $request->set_param( 'force', true ); $response = $this->server->dispatch( $request ); - $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( 404, $response->get_status() ); } /**