Skip to content

Commit 3776684

Browse files
committed
#8615: REST API unable to make requests with slash (/) in SKU
1 parent a1ee98a commit 3776684

File tree

8 files changed

+318
-18
lines changed

8 files changed

+318
-18
lines changed

app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
namespace Magento\Webapi\Controller\Rest;
88

9-
use Magento\Framework\Webapi\ServiceInputProcessor;
109
use Magento\Framework\Webapi\Rest\Request as RestRequest;
11-
use Magento\Webapi\Controller\Rest\Router;
10+
use Magento\Framework\Webapi\ServiceInputProcessor;
1211
use Magento\Webapi\Controller\Rest\Router\Route;
12+
use Magento\Webapi\Model\UrlDecoder;
1313

1414
/**
1515
* This class is responsible for retrieving resolved input data
@@ -47,26 +47,32 @@ class InputParamsResolver
4747
private $requestValidator;
4848

4949
/**
50-
* Initialize dependencies
51-
*
50+
* @var UrlDecoder
51+
*/
52+
private $urlDecoder;
53+
54+
/**
5255
* @param RestRequest $request
5356
* @param ParamsOverrider $paramsOverrider
5457
* @param ServiceInputProcessor $serviceInputProcessor
5558
* @param Router $router
5659
* @param RequestValidator $requestValidator
60+
* @param UrlDecoder $urlDecoder
5761
*/
5862
public function __construct(
5963
RestRequest $request,
6064
ParamsOverrider $paramsOverrider,
6165
ServiceInputProcessor $serviceInputProcessor,
6266
Router $router,
63-
RequestValidator $requestValidator
67+
RequestValidator $requestValidator,
68+
UrlDecoder $urlDecoder = null
6469
) {
6570
$this->request = $request;
6671
$this->paramsOverrider = $paramsOverrider;
6772
$this->serviceInputProcessor = $serviceInputProcessor;
6873
$this->router = $router;
6974
$this->requestValidator = $requestValidator;
75+
$this->urlDecoder = $urlDecoder ?: \Magento\Framework\App\ObjectManager::getInstance()->get(UrlDecoder::class);
7076
}
7177

7278
/**
@@ -97,6 +103,7 @@ public function resolve()
97103
$inputData = $this->request->getRequestData();
98104
}
99105

106+
$inputData = $this->urlDecoder->decodeParams($inputData);
100107
$inputData = $this->paramsOverrider->override($inputData, $route->getParameters());
101108
$inputParams = $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData);
102109
return $inputParams;

app/code/Magento/Webapi/Controller/Soap/Request/Handler.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Magento\Webapi\Model\Soap\Config as SoapConfig;
1818
use Magento\Framework\Reflection\MethodsMap;
1919
use Magento\Webapi\Model\ServiceMetadata;
20+
use Magento\Webapi\Model\UrlDecoder;
2021

2122
/**
2223
* Handler of requests to SOAP server.
@@ -70,8 +71,11 @@ class Handler
7071
protected $methodsMapProcessor;
7172

7273
/**
73-
* Initialize dependencies.
74-
*
74+
* @var UrlDecoder
75+
*/
76+
private $urlDecoder;
77+
78+
/**
7579
* @param SoapRequest $request
7680
* @param \Magento\Framework\ObjectManagerInterface $objectManager
7781
* @param SoapConfig $apiConfig
@@ -80,6 +84,7 @@ class Handler
8084
* @param ServiceInputProcessor $serviceInputProcessor
8185
* @param DataObjectProcessor $dataObjectProcessor
8286
* @param MethodsMap $methodsMapProcessor
87+
* @param UrlDecoder $urlDecoder
8388
*/
8489
public function __construct(
8590
SoapRequest $request,
@@ -89,7 +94,8 @@ public function __construct(
8994
SimpleDataObjectConverter $dataObjectConverter,
9095
ServiceInputProcessor $serviceInputProcessor,
9196
DataObjectProcessor $dataObjectProcessor,
92-
MethodsMap $methodsMapProcessor
97+
MethodsMap $methodsMapProcessor,
98+
UrlDecoder $urlDecoder = null
9399
) {
94100
$this->_request = $request;
95101
$this->_objectManager = $objectManager;
@@ -99,6 +105,7 @@ public function __construct(
99105
$this->serviceInputProcessor = $serviceInputProcessor;
100106
$this->_dataObjectProcessor = $dataObjectProcessor;
101107
$this->methodsMapProcessor = $methodsMapProcessor;
108+
$this->urlDecoder = $urlDecoder ?: \Magento\Framework\App\ObjectManager::getInstance()->get(UrlDecoder::class);
102109
}
103110

104111
/**
@@ -150,6 +157,7 @@ protected function _prepareRequestData($serviceClass, $serviceMethod, $arguments
150157
/** SoapServer wraps parameters into array. Thus this wrapping should be removed to get access to parameters. */
151158
$arguments = reset($arguments);
152159
$arguments = $this->_dataObjectConverter->convertStdObjectToArray($arguments, true);
160+
$arguments = $this->urlDecoder->decodeParams($arguments);
153161
return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $arguments);
154162
}
155163

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Webapi\Model;
8+
9+
/**
10+
* Url decoder.
11+
*/
12+
class UrlDecoder
13+
{
14+
/**
15+
* Decode request params.
16+
*
17+
* @param array $params
18+
*
19+
* @return array
20+
*/
21+
public function decodeParams(array $params)
22+
{
23+
foreach ($params as &$param) {
24+
if (is_array($param)) {
25+
$this->decodeParams($param);
26+
} else {
27+
if ($param !== null && is_string($param)) {
28+
$param = rawurldecode($param);
29+
}
30+
}
31+
}
32+
33+
return $params;
34+
}
35+
}

dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract
4747
ProductInterface::TYPE_ID => 'simple',
4848
ProductInterface::PRICE => 10
4949
],
50+
[
51+
ProductInterface::SKU => [
52+
'rest' => 'sku%252fwith%252fslashes',
53+
'soap' => 'sku%2fwith%2fslashes'
54+
],
55+
ProductInterface::NAME => 'Simple Product with Sku with Slashes',
56+
ProductInterface::TYPE_ID => 'simple',
57+
ProductInterface::PRICE => 10
58+
],
5059
];
5160

5261
/**
@@ -135,6 +144,20 @@ public function productCreationProvider()
135144
];
136145
}
137146

147+
/**
148+
* @magentoApiDataFixture Magento/Catalog/_files/product_simple_sku_with_slash.php
149+
*/
150+
public function testGetBySkuWithSlash()
151+
{
152+
$productData = $this->productData[2];
153+
$response = $this->getProduct($productData[ProductInterface::SKU][TESTS_WEB_API_ADAPTER]);
154+
$productData[ProductInterface::SKU] = rawurldecode($productData[ProductInterface::SKU]['soap']);
155+
foreach ([ProductInterface::SKU, ProductInterface::NAME, ProductInterface::PRICE] as $key) {
156+
$this->assertEquals($productData[$key], $response[$key]);
157+
}
158+
$this->assertEquals([1], $response[ProductInterface::EXTENSION_ATTRIBUTES_KEY]["website_ids"]);
159+
}
160+
138161
/**
139162
* Test removing association between product and website 1
140163
* @magentoApiDataFixture Magento/Catalog/_files/product_with_two_websites.php

dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php

Lines changed: 144 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,12 @@ public function setUp()
4949

5050
/**
5151
* @param array $result
52+
* @param string $productSku
53+
*
5254
* @return array
5355
*/
54-
protected function getStockItemBySku($result)
56+
protected function getStockItemBySku($result, $productSku)
5557
{
56-
$productSku = 'simple1';
5758
$serviceInfo = [
5859
'rest' => [
5960
'resourcePath' => self::RESOURCE_GET_PATH . "/$productSku",
@@ -69,22 +70,41 @@ protected function getStockItemBySku($result)
6970
$apiResult = $this->_webApiCall($serviceInfo, $arguments);
7071
$result['item_id'] = $apiResult['item_id'];
7172
$this->assertEquals($result, array_intersect_key($apiResult, $result), 'The stock data does not match.');
73+
7274
return $apiResult;
7375
}
7476

7577
/**
7678
* @param array $newData
7779
* @param array $expectedResult
7880
* @param array $fixtureData
81+
*
7982
* @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
8083
* @dataProvider saveStockItemBySkuWithWrongInputDataProvider
8184
*/
8285
public function testStockItemPUTWithWrongInput($newData, $expectedResult, $fixtureData)
8386
{
84-
$stockItemOld = $this->getStockItemBySku($fixtureData);
8587
$productSku = 'simple1';
88+
$stockItemOld = $this->getStockItemBySku($fixtureData, $productSku);
8689
$itemId = $stockItemOld['item_id'];
8790

91+
$actualData = $this->updateStockItemBySku($productSku, $itemId, $newData);
92+
93+
$this->assertEquals($stockItemOld['item_id'], $actualData);
94+
95+
/** @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory */
96+
$stockItemFactory = $this->objectManager
97+
->get(\Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory::class);
98+
$stockItem = $stockItemFactory->create();
99+
/** @var \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $stockItemResource */
100+
$stockItemResource = $this->objectManager->get(\Magento\CatalogInventory\Model\ResourceModel\Stock\Item::class);
101+
$stockItemResource->loadByProductId($stockItem, $stockItemOld['product_id'], $stockItemOld['stock_id']);
102+
$expectedResult['item_id'] = $stockItem->getItemId();
103+
$this->assertEquals($expectedResult, array_intersect_key($stockItem->getData(), $expectedResult));
104+
}
105+
106+
private function updateStockItemBySku($productSku, $itemId, $newData)
107+
{
88108
$resourcePath = str_replace([':productSku', ':itemId'], [$productSku, $itemId], self::RESOURCE_PUT_PATH);
89109

90110
$serviceInfo = [
@@ -113,7 +133,124 @@ public function testStockItemPUTWithWrongInput($newData, $expectedResult, $fixtu
113133
$data = $stockItemDetailsDo->getData();
114134
$data['show_default_notification_message'] = false;
115135
$arguments = ['productSku' => $productSku, 'stockItem' => $data];
116-
$this->assertEquals($stockItemOld['item_id'], $this->_webApiCall($serviceInfo, $arguments));
136+
137+
return $this->_webApiCall($serviceInfo, $arguments);
138+
}
139+
140+
/**
141+
* @return array
142+
*/
143+
public function saveStockItemBySkuWithWrongInputDataProvider()
144+
{
145+
return [
146+
[
147+
[
148+
'item_id' => 222,
149+
'product_id' => 222,
150+
'stock_id' => 1,
151+
'qty' => '111.0000',
152+
'min_qty' => '0.0000',
153+
'use_config_min_qty' => 1,
154+
'is_qty_decimal' => 0,
155+
'backorders' => 0,
156+
'use_config_backorders' => 1,
157+
'min_sale_qty' => '1.0000',
158+
'use_config_min_sale_qty' => 1,
159+
'max_sale_qty' => '0.0000',
160+
'use_config_max_sale_qty' => 1,
161+
'is_in_stock' => 1,
162+
'low_stock_date' => '',
163+
'notify_stock_qty' => null,
164+
'use_config_notify_stock_qty' => 1,
165+
'manage_stock' => 0,
166+
'use_config_manage_stock' => 1,
167+
'stock_status_changed_auto' => 0,
168+
'use_config_qty_increments' => 1,
169+
'qty_increments' => '0.0000',
170+
'use_config_enable_qty_inc' => 1,
171+
'enable_qty_increments' => 0,
172+
'is_decimal_divided' => 0,
173+
],
174+
[
175+
'item_id' => '1',
176+
'product_id' => '10',
177+
'stock_id' => '1',
178+
'qty' => '111.0000',
179+
'min_qty' => '0.0000',
180+
'use_config_min_qty' => '1',
181+
'is_qty_decimal' => '0',
182+
'backorders' => '0',
183+
'use_config_backorders' => '1',
184+
'min_sale_qty' => '1.0000',
185+
'use_config_min_sale_qty' => '1',
186+
'max_sale_qty' => '0.0000',
187+
'use_config_max_sale_qty' => '1',
188+
'is_in_stock' => '1',
189+
'low_stock_date' => null,
190+
'notify_stock_qty' => null,
191+
'use_config_notify_stock_qty' => '1',
192+
'manage_stock' => '0',
193+
'use_config_manage_stock' => '1',
194+
'stock_status_changed_auto' => '0',
195+
'use_config_qty_increments' => '1',
196+
'qty_increments' => '0.0000',
197+
'use_config_enable_qty_inc' => '1',
198+
'enable_qty_increments' => '0',
199+
'is_decimal_divided' => '0',
200+
'type_id' => 'simple',
201+
],
202+
[
203+
'item_id' => 1,
204+
'product_id' => 10,
205+
'stock_id' => 1,
206+
'qty' => 100,
207+
'is_in_stock' => 1,
208+
'is_qty_decimal' => '',
209+
'show_default_notification_message' => '',
210+
'use_config_min_qty' => 1,
211+
'min_qty' => 0,
212+
'use_config_min_sale_qty' => 1,
213+
'min_sale_qty' => 1,
214+
'use_config_max_sale_qty' => 1,
215+
'max_sale_qty' => 10000,
216+
'use_config_backorders' => 1,
217+
'backorders' => 0,
218+
'use_config_notify_stock_qty' => 1,
219+
'notify_stock_qty' => 1,
220+
'use_config_qty_increments' => 1,
221+
'qty_increments' => 0,
222+
'use_config_enable_qty_inc' => 1,
223+
'enable_qty_increments' => '',
224+
'use_config_manage_stock' => 1,
225+
'manage_stock' => 1,
226+
'low_stock_date' => '',
227+
'is_decimal_divided' => '',
228+
'stock_status_changed_auto' => 0,
229+
],
230+
],
231+
];
232+
}
233+
234+
/**
235+
* @param array $newData
236+
* @param array $expectedResult
237+
* @param array $fixtureData
238+
*
239+
* @magentoApiDataFixture Magento/Catalog/_files/product_simple_sku_with_slash.php
240+
* @dataProvider testUpdateStockItemBySkuDataProvider
241+
*/
242+
public function testUpdateStockItemBySku($newData, $expectedResult, $fixtureData)
243+
{
244+
$productSku = [
245+
'rest' => 'sku%252fwith%252fslashes',
246+
'soap' => 'sku%2fwith%2fslashes'
247+
];
248+
$stockItemOld = $this->getStockItemBySku($fixtureData, $productSku[TESTS_WEB_API_ADAPTER]);
249+
$itemId = $stockItemOld['item_id'];
250+
251+
$actualData = $this->updateStockItemBySku($productSku[TESTS_WEB_API_ADAPTER], $itemId, $newData);
252+
253+
$this->assertEquals($stockItemOld['item_id'], $actualData);
117254

118255
/** @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory */
119256
$stockItemFactory = $this->objectManager
@@ -126,10 +263,7 @@ public function testStockItemPUTWithWrongInput($newData, $expectedResult, $fixtu
126263
$this->assertEquals($expectedResult, array_intersect_key($stockItem->getData(), $expectedResult));
127264
}
128265

129-
/**
130-
* @return array
131-
*/
132-
public function saveStockItemBySkuWithWrongInputDataProvider()
266+
public function testUpdateStockItemBySkuDataProvider()
133267
{
134268
return [
135269
[
@@ -162,7 +296,7 @@ public function saveStockItemBySkuWithWrongInputDataProvider()
162296
],
163297
[
164298
'item_id' => '1',
165-
'product_id' => '10',
299+
'product_id' => '1',
166300
'stock_id' => '1',
167301
'qty' => '111.0000',
168302
'min_qty' => '0.0000',
@@ -190,7 +324,7 @@ public function saveStockItemBySkuWithWrongInputDataProvider()
190324
],
191325
[
192326
'item_id' => 1,
193-
'product_id' => 10,
327+
'product_id' => 1,
194328
'stock_id' => 1,
195329
'qty' => 100,
196330
'is_in_stock' => 1,

0 commit comments

Comments
 (0)