Skip to content
Snippets Groups Projects
Commit e9821764 authored by Oleksii Korshenko's avatar Oleksii Korshenko
Browse files

MAGETWO-85287: 8615: REST API unable to make requests with slash (/) in SKU #949

 - Merge Pull Request magento-engcom/magento2ce#949 from RomaKis/magento2:8615
 - Merged commits:
   1. 37766849
   2. 8870cf2a
parents 3e3e8fc9 8870cf2a
Branches
No related merge requests found
......@@ -6,10 +6,10 @@
namespace Magento\Webapi\Controller\Rest;
use Magento\Framework\Webapi\ServiceInputProcessor;
use Magento\Framework\Webapi\Rest\Request as RestRequest;
use Magento\Webapi\Controller\Rest\Router;
use Magento\Framework\Webapi\ServiceInputProcessor;
use Magento\Webapi\Controller\Rest\Router\Route;
use Magento\Webapi\Model\UrlDecoder;
/**
* This class is responsible for retrieving resolved input data
......@@ -47,26 +47,32 @@ class InputParamsResolver
private $requestValidator;
/**
* Initialize dependencies
*
* @var UrlDecoder
*/
private $urlDecoder;
/**
* @param RestRequest $request
* @param ParamsOverrider $paramsOverrider
* @param ServiceInputProcessor $serviceInputProcessor
* @param Router $router
* @param RequestValidator $requestValidator
* @param UrlDecoder $urlDecoder
*/
public function __construct(
RestRequest $request,
ParamsOverrider $paramsOverrider,
ServiceInputProcessor $serviceInputProcessor,
Router $router,
RequestValidator $requestValidator
RequestValidator $requestValidator,
UrlDecoder $urlDecoder = null
) {
$this->request = $request;
$this->paramsOverrider = $paramsOverrider;
$this->serviceInputProcessor = $serviceInputProcessor;
$this->router = $router;
$this->requestValidator = $requestValidator;
$this->urlDecoder = $urlDecoder ?: \Magento\Framework\App\ObjectManager::getInstance()->get(UrlDecoder::class);
}
/**
......@@ -97,6 +103,7 @@ class InputParamsResolver
$inputData = $this->request->getRequestData();
}
$inputData = $this->urlDecoder->decodeParams($inputData);
$inputData = $this->paramsOverrider->override($inputData, $route->getParameters());
$inputParams = $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData);
return $inputParams;
......
......@@ -17,6 +17,7 @@ use Magento\Framework\Webapi\Exception as WebapiException;
use Magento\Webapi\Model\Soap\Config as SoapConfig;
use Magento\Framework\Reflection\MethodsMap;
use Magento\Webapi\Model\ServiceMetadata;
use Magento\Webapi\Model\UrlDecoder;
/**
* Handler of requests to SOAP server.
......@@ -70,8 +71,11 @@ class Handler
protected $methodsMapProcessor;
/**
* Initialize dependencies.
*
* @var UrlDecoder
*/
private $urlDecoder;
/**
* @param SoapRequest $request
* @param \Magento\Framework\ObjectManagerInterface $objectManager
* @param SoapConfig $apiConfig
......@@ -80,6 +84,7 @@ class Handler
* @param ServiceInputProcessor $serviceInputProcessor
* @param DataObjectProcessor $dataObjectProcessor
* @param MethodsMap $methodsMapProcessor
* @param UrlDecoder $urlDecoder
*/
public function __construct(
SoapRequest $request,
......@@ -89,7 +94,8 @@ class Handler
SimpleDataObjectConverter $dataObjectConverter,
ServiceInputProcessor $serviceInputProcessor,
DataObjectProcessor $dataObjectProcessor,
MethodsMap $methodsMapProcessor
MethodsMap $methodsMapProcessor,
UrlDecoder $urlDecoder = null
) {
$this->_request = $request;
$this->_objectManager = $objectManager;
......@@ -99,6 +105,7 @@ class Handler
$this->serviceInputProcessor = $serviceInputProcessor;
$this->_dataObjectProcessor = $dataObjectProcessor;
$this->methodsMapProcessor = $methodsMapProcessor;
$this->urlDecoder = $urlDecoder ?: \Magento\Framework\App\ObjectManager::getInstance()->get(UrlDecoder::class);
}
/**
......@@ -150,6 +157,7 @@ class Handler
/** SoapServer wraps parameters into array. Thus this wrapping should be removed to get access to parameters. */
$arguments = reset($arguments);
$arguments = $this->_dataObjectConverter->convertStdObjectToArray($arguments, true);
$arguments = $this->urlDecoder->decodeParams($arguments);
return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $arguments);
}
......
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Webapi\Model;
/**
* Url decoder.
*/
class UrlDecoder
{
/**
* Decode request params.
*
* @param array $params
*
* @return array
*/
public function decodeParams(array $params)
{
foreach ($params as &$param) {
if (is_array($param)) {
$param = $this->decodeParams($param);
} else {
if ($param !== null && is_string($param)) {
$param = rawurldecode($param);
}
}
}
return $params;
}
}
......@@ -47,6 +47,15 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract
ProductInterface::TYPE_ID => 'simple',
ProductInterface::PRICE => 10
],
[
ProductInterface::SKU => [
'rest' => 'sku%252fwith%252fslashes',
'soap' => 'sku%2fwith%2fslashes'
],
ProductInterface::NAME => 'Simple Product with Sku with Slashes',
ProductInterface::TYPE_ID => 'simple',
ProductInterface::PRICE => 10
],
];
/**
......@@ -135,6 +144,20 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract
];
}
/**
* @magentoApiDataFixture Magento/Catalog/_files/product_simple_sku_with_slash.php
*/
public function testGetBySkuWithSlash()
{
$productData = $this->productData[2];
$response = $this->getProduct($productData[ProductInterface::SKU][TESTS_WEB_API_ADAPTER]);
$productData[ProductInterface::SKU] = rawurldecode($productData[ProductInterface::SKU]['soap']);
foreach ([ProductInterface::SKU, ProductInterface::NAME, ProductInterface::PRICE] as $key) {
$this->assertEquals($productData[$key], $response[$key]);
}
$this->assertEquals([1], $response[ProductInterface::EXTENSION_ATTRIBUTES_KEY]["website_ids"]);
}
/**
* Test removing association between product and website 1
* @magentoApiDataFixture Magento/Catalog/_files/product_with_two_websites.php
......
......@@ -49,11 +49,12 @@ class StockItemTest extends WebapiAbstract
/**
* @param array $result
* @param string $productSku
*
* @return array
*/
protected function getStockItemBySku($result)
protected function getStockItemBySku($result, $productSku)
{
$productSku = 'simple1';
$serviceInfo = [
'rest' => [
'resourcePath' => self::RESOURCE_GET_PATH . "/$productSku",
......@@ -69,6 +70,7 @@ class StockItemTest extends WebapiAbstract
$apiResult = $this->_webApiCall($serviceInfo, $arguments);
$result['item_id'] = $apiResult['item_id'];
$this->assertEquals($result, array_intersect_key($apiResult, $result), 'The stock data does not match.');
return $apiResult;
}
......@@ -76,15 +78,33 @@ class StockItemTest extends WebapiAbstract
* @param array $newData
* @param array $expectedResult
* @param array $fixtureData
*
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
* @dataProvider saveStockItemBySkuWithWrongInputDataProvider
*/
public function testStockItemPUTWithWrongInput($newData, $expectedResult, $fixtureData)
{
$stockItemOld = $this->getStockItemBySku($fixtureData);
$productSku = 'simple1';
$stockItemOld = $this->getStockItemBySku($fixtureData, $productSku);
$itemId = $stockItemOld['item_id'];
$actualData = $this->updateStockItemBySku($productSku, $itemId, $newData);
$this->assertEquals($stockItemOld['item_id'], $actualData);
/** @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory */
$stockItemFactory = $this->objectManager
->get(\Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory::class);
$stockItem = $stockItemFactory->create();
/** @var \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $stockItemResource */
$stockItemResource = $this->objectManager->get(\Magento\CatalogInventory\Model\ResourceModel\Stock\Item::class);
$stockItemResource->loadByProductId($stockItem, $stockItemOld['product_id'], $stockItemOld['stock_id']);
$expectedResult['item_id'] = $stockItem->getItemId();
$this->assertEquals($expectedResult, array_intersect_key($stockItem->getData(), $expectedResult));
}
private function updateStockItemBySku($productSku, $itemId, $newData)
{
$resourcePath = str_replace([':productSku', ':itemId'], [$productSku, $itemId], self::RESOURCE_PUT_PATH);
$serviceInfo = [
......@@ -113,7 +133,124 @@ class StockItemTest extends WebapiAbstract
$data = $stockItemDetailsDo->getData();
$data['show_default_notification_message'] = false;
$arguments = ['productSku' => $productSku, 'stockItem' => $data];
$this->assertEquals($stockItemOld['item_id'], $this->_webApiCall($serviceInfo, $arguments));
return $this->_webApiCall($serviceInfo, $arguments);
}
/**
* @return array
*/
public function saveStockItemBySkuWithWrongInputDataProvider()
{
return [
[
[
'item_id' => 222,
'product_id' => 222,
'stock_id' => 1,
'qty' => '111.0000',
'min_qty' => '0.0000',
'use_config_min_qty' => 1,
'is_qty_decimal' => 0,
'backorders' => 0,
'use_config_backorders' => 1,
'min_sale_qty' => '1.0000',
'use_config_min_sale_qty' => 1,
'max_sale_qty' => '0.0000',
'use_config_max_sale_qty' => 1,
'is_in_stock' => 1,
'low_stock_date' => '',
'notify_stock_qty' => null,
'use_config_notify_stock_qty' => 1,
'manage_stock' => 0,
'use_config_manage_stock' => 1,
'stock_status_changed_auto' => 0,
'use_config_qty_increments' => 1,
'qty_increments' => '0.0000',
'use_config_enable_qty_inc' => 1,
'enable_qty_increments' => 0,
'is_decimal_divided' => 0,
],
[
'item_id' => '1',
'product_id' => '10',
'stock_id' => '1',
'qty' => '111.0000',
'min_qty' => '0.0000',
'use_config_min_qty' => '1',
'is_qty_decimal' => '0',
'backorders' => '0',
'use_config_backorders' => '1',
'min_sale_qty' => '1.0000',
'use_config_min_sale_qty' => '1',
'max_sale_qty' => '0.0000',
'use_config_max_sale_qty' => '1',
'is_in_stock' => '1',
'low_stock_date' => null,
'notify_stock_qty' => null,
'use_config_notify_stock_qty' => '1',
'manage_stock' => '0',
'use_config_manage_stock' => '1',
'stock_status_changed_auto' => '0',
'use_config_qty_increments' => '1',
'qty_increments' => '0.0000',
'use_config_enable_qty_inc' => '1',
'enable_qty_increments' => '0',
'is_decimal_divided' => '0',
'type_id' => 'simple',
],
[
'item_id' => 1,
'product_id' => 10,
'stock_id' => 1,
'qty' => 100,
'is_in_stock' => 1,
'is_qty_decimal' => '',
'show_default_notification_message' => '',
'use_config_min_qty' => 1,
'min_qty' => 0,
'use_config_min_sale_qty' => 1,
'min_sale_qty' => 1,
'use_config_max_sale_qty' => 1,
'max_sale_qty' => 10000,
'use_config_backorders' => 1,
'backorders' => 0,
'use_config_notify_stock_qty' => 1,
'notify_stock_qty' => 1,
'use_config_qty_increments' => 1,
'qty_increments' => 0,
'use_config_enable_qty_inc' => 1,
'enable_qty_increments' => '',
'use_config_manage_stock' => 1,
'manage_stock' => 1,
'low_stock_date' => '',
'is_decimal_divided' => '',
'stock_status_changed_auto' => 0,
],
],
];
}
/**
* @param array $newData
* @param array $expectedResult
* @param array $fixtureData
*
* @magentoApiDataFixture Magento/Catalog/_files/product_simple_sku_with_slash.php
* @dataProvider testUpdateStockItemBySkuDataProvider
*/
public function testUpdateStockItemBySku($newData, $expectedResult, $fixtureData)
{
$productSku = [
'rest' => 'sku%252fwith%252fslashes',
'soap' => 'sku%2fwith%2fslashes'
];
$stockItemOld = $this->getStockItemBySku($fixtureData, $productSku[TESTS_WEB_API_ADAPTER]);
$itemId = $stockItemOld['item_id'];
$actualData = $this->updateStockItemBySku($productSku[TESTS_WEB_API_ADAPTER], $itemId, $newData);
$this->assertEquals($stockItemOld['item_id'], $actualData);
/** @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory */
$stockItemFactory = $this->objectManager
......@@ -126,10 +263,7 @@ class StockItemTest extends WebapiAbstract
$this->assertEquals($expectedResult, array_intersect_key($stockItem->getData(), $expectedResult));
}
/**
* @return array
*/
public function saveStockItemBySkuWithWrongInputDataProvider()
public function testUpdateStockItemBySkuDataProvider()
{
return [
[
......@@ -162,7 +296,7 @@ class StockItemTest extends WebapiAbstract
],
[
'item_id' => '1',
'product_id' => '10',
'product_id' => '1',
'stock_id' => '1',
'qty' => '111.0000',
'min_qty' => '0.0000',
......@@ -190,7 +324,7 @@ class StockItemTest extends WebapiAbstract
],
[
'item_id' => 1,
'product_id' => 10,
'product_id' => 1,
'stock_id' => 1,
'qty' => 100,
'is_in_stock' => 1,
......
......@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\CatalogInventory\Api;
use Magento\TestFramework\Helper\Bootstrap;
......@@ -27,6 +28,14 @@ class StockStatusTest extends WebapiAbstract
/** @var \Magento\Catalog\Model\Product $product */
$product = $objectManager->get(\Magento\Catalog\Model\Product::class)->load(1);
$expectedData = $product->getQuantityAndStockStatus();
$actualData = $this->getProductStockStatus($productSku);
$this->assertArrayHasKey('stock_item', $actualData);
$this->assertEquals($expectedData['is_in_stock'], $actualData['stock_item']['is_in_stock']);
$this->assertEquals($expectedData['qty'], $actualData['stock_item']['qty']);
}
private function getProductStockStatus($productSku)
{
$serviceInfo = [
'rest' => [
'resourcePath' => self::RESOURCE_PATH . "/$productSku",
......@@ -41,6 +50,25 @@ class StockStatusTest extends WebapiAbstract
$requestData = ['productSku' => $productSku];
$actualData = $this->_webApiCall($serviceInfo, $requestData);
return $actualData;
}
/**
* @magentoApiDataFixture Magento/Catalog/_files/product_simple_sku_with_slash.php
*/
public function testGetProductStockStatusBySkuWithSlashes()
{
$productSku = [
'rest' => 'sku%252fwith%252fslashes',
'soap' => 'sku%2fwith%2fslashes'
];
$objectManager = Bootstrap::getObjectManager();
/** @var \Magento\Catalog\Model\Product $product */
$product = $objectManager->get(\Magento\Catalog\Model\Product::class)->load(1);
$expectedData = $product->getQuantityAndStockStatus();
$actualData = $this->getProductStockStatus($productSku[TESTS_WEB_API_ADAPTER]);
$this->assertArrayHasKey('stock_item', $actualData);
$this->assertEquals($expectedData['is_in_stock'], $actualData['stock_item']['is_in_stock']);
$this->assertEquals($expectedData['qty'], $actualData['stock_item']['qty']);
......
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/** @var \Magento\TestFramework\ObjectManager $objectManager */
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
/** @var $product \Magento\Catalog\Model\Product */
$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
$product->isObjectNew(true);
$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
->setId(1)
->setAttributeSetId(4)
->setWebsiteIds([1])
->setName('Simple Product with Sku with Slashes')
->setSku('sku/with/slashes')
->setPrice(10)
->setWeight(1)
->setShortDescription("Short description")
->setTaxClassId(0)
->setDescription('Description with <b>html tag</b>')
->setMetaTitle('meta title')
->setMetaKeyword('meta keyword')
->setMetaDescription('meta description')
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
->setStockData(
[
'use_config_manage_stock' => 1,
'qty' => 100,
'is_qty_decimal' => 0,
'is_in_stock' => 1,
]
)->setCanSaveCustomOptions(true)
->setHasOptions(true);
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */
$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
$productRepository->save($product);
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
use Magento\Framework\Exception\NoSuchEntityException;
/** @var \Magento\Framework\Registry $registry */
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
$registry->unregister('isSecureArea');
$registry->register('isSecureArea', true);
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
try {
$product = $productRepository->get('sku/with/slashes', false, null, true);
$productRepository->delete($product);
} catch (NoSuchEntityException $e) {
}
$registry->unregister('isSecureArea');
$registry->register('isSecureArea', false);
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment