* Reduce repetition in SchemaController::initialize

* Ensure AbstractCartRoute has a cart schema when it returns errors

* Move nonce logic to cart

* typo
This commit is contained in:
Mike Jolley 2021-02-11 16:26:52 +00:00 committed by GitHub
parent 39ab3070c8
commit 66fbd9738b
8 changed files with 183 additions and 170 deletions

View File

@ -2,6 +2,8 @@
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema;
/**
* Abstract Cart Route
@ -9,6 +11,32 @@ use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
*/
abstract class AbstractCartRoute extends AbstractRoute {
/**
* Schema class for this route's response.
*
* @var AbstractSchema|CartSchema
*/
protected $schema;
/**
* Schema class for the cart.
*
* @var CartSchema
*/
protected $cart_schema;
/**
* Constructor accepts two types of schema; one for the item being returned, and one for the cart as a whole. These
* may be the same depending on the route.
*
* @param CartSchema $cart_schema Schema class for the cart.
* @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema.
*/
public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null ) {
$this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema;
$this->cart_schema = $cart_schema;
}
/**
* Get the route response based on the type of request.
*
@ -18,7 +46,37 @@ abstract class AbstractCartRoute extends AbstractRoute {
public function get_response( \WP_REST_Request $request ) {
$this->maybe_load_cart();
$this->calculate_totals();
return parent::get_response( $request );
try {
if ( $this->requires_nonce( $request ) ) {
$this->check_nonce( $request );
}
$response = parent::get_response( $request );
} catch ( RouteException $error ) {
$response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() );
} catch ( \Exception $error ) {
$response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 );
}
if ( is_wp_error( $response ) ) {
$response = $this->error_to_response( $response );
}
$response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
$response->header( 'X-WC-Store-API-Nonce-Timestamp', time() );
$response->header( 'X-WC-Store-API-User', get_current_user_id() );
return $response;
}
/**
* Checks if a nonce is required for the route.
*
* @param \WP_REST_Request $request Request.
* @return bool
*/
protected function requires_nonce( \WP_REST_Request $request ) {
return 'GET' !== $request->get_method();
}
/**
@ -46,6 +104,34 @@ abstract class AbstractCartRoute extends AbstractRoute {
wc_release_stock_for_order( $draft_order );
}
/**
* For non-GET endpoints, require and validate a nonce to prevent CSRF attacks.
*
* Nonces will mismatch if the logged in session cookie is different! If using a client to test, set this cookie
* to match the logged in cookie in your browser.
*
* @throws RouteException On error.
*
* @param \WP_REST_Request $request Request object.
*/
protected function check_nonce( \WP_REST_Request $request ) {
$nonce = $request->get_header( 'X-WC-Store-API-Nonce' );
if ( apply_filters( 'woocommerce_store_api_disable_nonce_check', false ) ) {
return;
}
if ( null === $nonce ) {
throw new RouteException( 'woocommerce_rest_missing_nonce', __( 'Missing the X-WC-Store-API-Nonce header. This endpoint requires a valid nonce.', 'woo-gutenberg-products-block' ), 401 );
}
$valid_nonce = wp_verify_nonce( $nonce, 'wc_store_api' );
if ( ! $valid_nonce ) {
throw new RouteException( 'woocommerce_rest_invalid_nonce', __( 'X-WC-Store-API-Nonce is invalid.', 'woo-gutenberg-products-block' ), 403 );
}
}
/**
* Get route response when something went wrong.
*
@ -69,11 +155,22 @@ abstract class AbstractCartRoute extends AbstractRoute {
$additional_data,
[
'status' => $http_status_code,
'cart' => $this->schema->get_item_response( $cart ),
'cart' => $this->cart_schema->get_item_response( $cart ),
]
)
);
}
return new \WP_Error( $error_code, $error_message, [ 'status' => $http_status_code ] );
}
/**
* Makes the cart and sessions available to a route by loading them from core.
*/
protected function maybe_load_cart() {
if ( ! did_action( 'woocommerce_load_cart_from_session' ) && function_exists( 'wc_load_cart' ) ) {
include_once WC_ABSPATH . 'includes/wc-cart-functions.php';
include_once WC_ABSPATH . 'includes/wc-notice-functions.php';
wc_load_cart();
}
}
}

View File

@ -52,9 +52,6 @@ abstract class AbstractRoute implements RouteInterface {
public function get_response( \WP_REST_Request $request ) {
$response = null;
try {
if ( 'GET' !== $request->get_method() ) {
$this->check_nonce( $request );
}
switch ( $request->get_method() ) {
case 'POST':
$response = $this->get_route_post_response( $request );
@ -80,9 +77,6 @@ abstract class AbstractRoute implements RouteInterface {
$response = $this->error_to_response( $response );
}
$response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
$response->header( 'X-WC-Store-API-Nonce-Timestamp', time() );
$response->header( 'X-WC-Store-API-User', get_current_user_id() );
return $response;
}
@ -116,34 +110,6 @@ abstract class AbstractRoute implements RouteInterface {
return new \WP_REST_Response( $data, $status );
}
/**
* For non-GET endpoints, require and validate a nonce to prevent CSRF attacks.
*
* Nonces will mismatch if the logged in session cookie is different! If using a client to test, set this cookie
* to match the logged in cookie in your browser.
*
* @throws RouteException On error.
*
* @param \WP_REST_Request $request Request object.
*/
protected function check_nonce( \WP_REST_Request $request ) {
$nonce = $request->get_header( 'X-WC-Store-API-Nonce' );
if ( apply_filters( 'woocommerce_store_api_disable_nonce_check', false ) ) {
return;
}
if ( null === $nonce ) {
throw new RouteException( 'woocommerce_rest_missing_nonce', __( 'Missing the X-WC-Store-API-Nonce header. This endpoint requires a valid nonce.', 'woo-gutenberg-products-block' ), 401 );
}
$valid_nonce = wp_verify_nonce( $nonce, 'wc_store_api' );
if ( ! $valid_nonce ) {
throw new RouteException( 'woocommerce_rest_invalid_nonce', __( 'X-WC-Store-API-Nonce is invalid.', 'woo-gutenberg-products-block' ), 403 );
}
}
/**
* Get route response for GET requests.
*
@ -296,15 +262,4 @@ abstract class AbstractRoute implements RouteInterface {
'context' => $this->get_context_param(),
);
}
/**
* Makes the cart and sessions available to a route by loading them from core.
*/
protected function maybe_load_cart() {
if ( ! did_action( 'woocommerce_load_cart_from_session' ) && function_exists( 'wc_load_cart' ) ) {
include_once WC_ABSPATH . 'includes/wc-cart-functions.php';
include_once WC_ABSPATH . 'includes/wc-notice-functions.php';
wc_load_cart();
}
}
}

View File

@ -14,33 +14,6 @@ use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ShippingAddressSchema;
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
*/
class CartUpdateCustomer extends AbstractCartRoute {
/**
* Billing address schema instance.
*
* @var BillingAddressSchema
*/
protected $billing_address_schema;
/**
* Shipping address schema instance.
*
* @var ShippingAddressSchema
*/
protected $shipping_address_schema;
/**
* Constructor.
*
* @param CartSchema $schema Schema class for this route.
* @param ShippingAddressSchema $shipping_address_schema Billing address schema class for this route.
* @param BillingAddressSchema $billing_address_schema Billing address schema class for this route.
*/
public function __construct( CartSchema $schema, ShippingAddressSchema $shipping_address_schema, BillingAddressSchema $billing_address_schema ) {
$this->schema = $schema;
$this->shipping_address_schema = $shipping_address_schema;
$this->billing_address_schema = $billing_address_schema;
}
/**
* Get the namespace for this route.
*
@ -75,17 +48,17 @@ class CartUpdateCustomer extends AbstractCartRoute {
'description' => __( 'Billing address.', 'woo-gutenberg-products-block' ),
'type' => 'object',
'context' => [ 'view', 'edit' ],
'properties' => $this->billing_address_schema->get_properties(),
'sanitize_callback' => [ $this->billing_address_schema, 'sanitize_callback' ],
'validate_callback' => [ $this->billing_address_schema, 'validate_callback' ],
'properties' => $this->schema->billing_address_schema->get_properties(),
'sanitize_callback' => [ $this->schema->billing_address_schema, 'sanitize_callback' ],
'validate_callback' => [ $this->schema->billing_address_schema, 'validate_callback' ],
],
'shipping_address' => [
'description' => __( 'Shipping address.', 'woo-gutenberg-products-block' ),
'type' => 'object',
'context' => [ 'view', 'edit' ],
'properties' => $this->shipping_address_schema->get_properties(),
'sanitize_callback' => [ $this->shipping_address_schema, 'sanitize_callback' ],
'validate_callback' => [ $this->shipping_address_schema, 'validate_callback' ],
'properties' => $this->schema->shipping_address_schema->get_properties(),
'sanitize_callback' => [ $this->schema->shipping_address_schema, 'sanitize_callback' ],
'validate_callback' => [ $this->schema->shipping_address_schema, 'validate_callback' ],
],
],
],

View File

@ -21,7 +21,7 @@ use Automattic\WooCommerce\Blocks\Payments\PaymentContext;
*
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
*/
class Checkout extends AbstractRoute {
class Checkout extends AbstractCartRoute {
/**
* Holds the current order being processed.
*
@ -39,23 +39,13 @@ class Checkout extends AbstractRoute {
}
/**
* Enforce nonces for all checkout endpoints.
* Checks if a nonce is required for the route.
*
* @param WP_REST_Request $request Request object.
* @return WP_Error|WP_REST_Response
* @param \WP_REST_Request $request Request.
* @return bool
*/
public function get_response( WP_REST_Request $request ) {
$this->maybe_load_cart();
$response = null;
try {
$this->check_nonce( $request );
$response = parent::get_response( $request );
} catch ( RouteException $error ) {
$response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode() );
} catch ( Exception $error ) {
$response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 );
}
return $response;
protected function requires_nonce( \WP_REST_Request $request ) {
return true;
}
/**

View File

@ -44,8 +44,6 @@ class Products extends AbstractRoute {
* @return \WP_REST_Response
*/
protected function get_route_response( \WP_REST_Request $request ) {
// we load so that we have the same session, this is done so that Add to Cart can function.
$this->maybe_load_cart();
$response = new \WP_REST_Response();
$product_query = new ProductQuery();

View File

@ -70,16 +70,16 @@ class RoutesController {
'cart' => new Routes\Cart( $this->schemas->get( 'cart' ) ),
'cart-add-item' => new Routes\CartAddItem( $this->schemas->get( 'cart' ) ),
'cart-apply-coupon' => new Routes\CartApplyCoupon( $this->schemas->get( 'cart' ) ),
'cart-coupons' => new Routes\CartCoupons( $this->schemas->get( 'cart-coupon' ) ),
'cart-coupons-by-code' => new Routes\CartCouponsByCode( $this->schemas->get( 'cart-coupon' ) ),
'cart-items' => new Routes\CartItems( $this->schemas->get( 'cart-item' ) ),
'cart-items-by-key' => new Routes\CartItemsByKey( $this->schemas->get( 'cart-item' ) ),
'cart-coupons' => new Routes\CartCoupons( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ) ),
'cart-coupons-by-code' => new Routes\CartCouponsByCode( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ) ),
'cart-items' => new Routes\CartItems( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ) ),
'cart-items-by-key' => new Routes\CartItemsByKey( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ) ),
'cart-remove-coupon' => new Routes\CartRemoveCoupon( $this->schemas->get( 'cart' ) ),
'cart-remove-item' => new Routes\CartRemoveItem( $this->schemas->get( 'cart' ) ),
'cart-select-shipping-rate' => new Routes\CartSelectShippingRate( $this->schemas->get( 'cart' ) ),
'cart-update-item' => new Routes\CartUpdateItem( $this->schemas->get( 'cart' ) ),
'cart-update-customer' => new Routes\CartUpdateCustomer( $this->schemas->get( 'cart' ), $this->schemas->get( 'shipping-address' ), $this->schemas->get( 'billing-address' ) ),
'checkout' => new Routes\Checkout( $this->schemas->get( 'checkout' ) ),
'cart-update-customer' => new Routes\CartUpdateCustomer( $this->schemas->get( 'cart' ) ),
'checkout' => new Routes\Checkout( $this->schemas->get( 'cart' ), $this->schemas->get( 'checkout' ) ),
'product-attributes' => new Routes\ProductAttributes( $this->schemas->get( 'product-attribute' ) ),
'product-attributes-by-id' => new Routes\ProductAttributesById( $this->schemas->get( 'product-attribute' ) ),
'product-attribute-terms' => new Routes\ProductAttributeTerms( $this->schemas->get( 'term' ) ),

View File

@ -2,7 +2,23 @@
namespace Automattic\WooCommerce\Blocks\StoreApi;
use Exception;
use Schemas\AbstractSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\BillingAddressSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ShippingAddressSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartShippingRateSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartItemSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartCouponSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartFeeSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ErrorSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ImageAttachmentSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductAttributeSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductCategorySchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductCollectionDataSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductReviewSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\TermSchema;
use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi;
@ -56,63 +72,47 @@ class SchemaController {
* Load schema class instances.
*/
protected function initialize() {
$this->schemas = [
Schemas\BillingAddressSchema::IDENTIFIER => new Schemas\BillingAddressSchema(
$this->extend
),
Schemas\ShippingAddressSchema::IDENTIFIER => new Schemas\ShippingAddressSchema(
$this->extend
),
Schemas\CartShippingRateSchema::IDENTIFIER => new Schemas\CartShippingRateSchema(
$this->extend
),
Schemas\CartSchema::IDENTIFIER => new Schemas\CartSchema(
$this->extend,
new Schemas\CartItemSchema(
$this->extend,
new Schemas\ImageAttachmentSchema( $this->extend )
),
new Schemas\CartCouponSchema( $this->extend ),
new Schemas\CartFeeSchema( $this->extend ),
new Schemas\CartShippingRateSchema( $this->extend ),
new Schemas\ShippingAddressSchema( $this->extend ),
new Schemas\BillingAddressSchema( $this->extend ),
new Schemas\ErrorSchema( $this->extend )
),
Schemas\CartCouponSchema::IDENTIFIER => new Schemas\CartCouponSchema( $this->extend ),
Schemas\CartItemSchema::IDENTIFIER => new Schemas\CartItemSchema(
$this->extend,
new Schemas\ImageAttachmentSchema( $this->extend )
),
Schemas\CartFeeSchema::IDENTIFIER => new Schemas\CartFeeSchema( $this->extend ),
Schemas\CheckoutSchema::IDENTIFIER => new Schemas\CheckoutSchema(
$this->extend,
new Schemas\BillingAddressSchema( $this->extend ),
new Schemas\ShippingAddressSchema(
$this->extend
)
),
Schemas\ProductSchema::IDENTIFIER => new Schemas\ProductSchema(
$this->extend,
new Schemas\ImageAttachmentSchema(
$this->extend
)
),
Schemas\ProductAttributeSchema::IDENTIFIER => new Schemas\ProductAttributeSchema( $this->extend ),
Schemas\ProductCategorySchema::IDENTIFIER => new Schemas\ProductCategorySchema(
$this->extend,
new Schemas\ImageAttachmentSchema( $this->extend )
),
Schemas\ProductCollectionDataSchema::IDENTIFIER => new Schemas\ProductCollectionDataSchema(
$this->extend
),
Schemas\ProductReviewSchema::IDENTIFIER => new Schemas\ProductReviewSchema(
$this->extend,
new Schemas\ImageAttachmentSchema( $this->extend )
),
Schemas\TermSchema::IDENTIFIER => new Schemas\TermSchema(
$this->extend
),
];
$this->schemas = [];
$this->schemas[ ErrorSchema::IDENTIFIER ] = new ErrorSchema( $this->extend );
$this->schemas[ ImageAttachmentSchema::IDENTIFIER ] = new ImageAttachmentSchema( $this->extend );
$this->schemas[ TermSchema::IDENTIFIER ] = new TermSchema( $this->extend );
$this->schemas[ BillingAddressSchema::IDENTIFIER ] = new BillingAddressSchema( $this->extend );
$this->schemas[ ShippingAddressSchema::IDENTIFIER ] = new ShippingAddressSchema( $this->extend );
$this->schemas[ CartShippingRateSchema::IDENTIFIER ] = new CartShippingRateSchema( $this->extend );
$this->schemas[ CartCouponSchema::IDENTIFIER ] = new CartCouponSchema( $this->extend );
$this->schemas[ CartFeeSchema::IDENTIFIER ] = new CartFeeSchema( $this->extend );
$this->schemas[ CartItemSchema::IDENTIFIER ] = new CartItemSchema(
$this->extend,
$this->schemas[ ImageAttachmentSchema::IDENTIFIER ]
);
$this->schemas[ CartSchema::IDENTIFIER ] = new CartSchema(
$this->extend,
$this->schemas[ CartItemSchema::IDENTIFIER ],
$this->schemas[ CartCouponSchema::IDENTIFIER ],
$this->schemas[ CartFeeSchema::IDENTIFIER ],
$this->schemas[ CartShippingRateSchema::IDENTIFIER ],
$this->schemas[ ShippingAddressSchema::IDENTIFIER ],
$this->schemas[ BillingAddressSchema::IDENTIFIER ],
$this->schemas[ ErrorSchema::IDENTIFIER ]
);
$this->schemas[ CheckoutSchema::IDENTIFIER ] = new CheckoutSchema(
$this->extend,
$this->schemas[ BillingAddressSchema::IDENTIFIER ],
$this->schemas[ ShippingAddressSchema::IDENTIFIER ]
);
$this->schemas[ ProductSchema::IDENTIFIER ] = new ProductSchema(
$this->extend,
$this->schemas[ ImageAttachmentSchema::IDENTIFIER ]
);
$this->schemas[ ProductAttributeSchema::IDENTIFIER ] = new ProductAttributeSchema( $this->extend );
$this->schemas[ ProductCategorySchema::IDENTIFIER ] = new ProductCategorySchema(
$this->extend,
$this->schemas[ ImageAttachmentSchema::IDENTIFIER ]
);
$this->schemas[ ProductCollectionDataSchema::IDENTIFIER ] = new ProductCollectionDataSchema( $this->extend );
$this->schemas[ ProductReviewSchema::IDENTIFIER ] = new ProductReviewSchema(
$this->extend,
$this->schemas[ ImageAttachmentSchema::IDENTIFIER ]
);
}
}

View File

@ -31,49 +31,49 @@ class CartSchema extends AbstractSchema {
*
* @var CartItemSchema
*/
protected $item_schema;
public $item_schema;
/**
* Coupon schema instance.
*
* @var CartCouponSchema
*/
protected $coupon_schema;
public $coupon_schema;
/**
* Fee schema instance.
*
* @var CartFeeSchema
*/
protected $fee_schema;
public $fee_schema;
/**
* Shipping rates schema instance.
*
* @var CartShippingRateSchema
*/
protected $shipping_rate_schema;
public $shipping_rate_schema;
/**
* Shipping address schema instance.
*
* @var ShippingAddressSchema
*/
protected $shipping_address_schema;
public $shipping_address_schema;
/**
* Billing address schema instance.
*
* @var BillingAddressSchema
*/
protected $billing_address_schema;
public $billing_address_schema;
/**
* Error schema instance.
*
* @var ErrorSchema
*/
protected $error_schema;
public $error_schema;
/**
* Constructor.