Skip to content

Commit a17a621

Browse files
committed
ACP2E-340: [Magento Cloud] Empty API request causes 500
1 parent a96f0f7 commit a17a621

File tree

7 files changed

+186
-14
lines changed

7 files changed

+186
-14
lines changed

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

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

77
namespace Magento\Webapi\Controller;
88

9-
use Magento\Framework\App\DeploymentConfig;
10-
use Magento\Framework\Config\ConfigOptionsListConstants;
119
use Magento\Framework\Exception\AuthorizationException;
1210
use Magento\Framework\Webapi\Authorization;
1311
use Magento\Framework\Webapi\ErrorProcessor;
14-
use Magento\Framework\Webapi\Request;
1512
use Magento\Framework\Webapi\Rest\Request as RestRequest;
13+
use Magento\Framework\Webapi\Rest\RequestValidatorInterface;
1614
use Magento\Framework\Webapi\Rest\Response as RestResponse;
1715
use Magento\Framework\Webapi\ServiceInputProcessor;
1816
use Magento\Store\Model\Store;
@@ -35,7 +33,7 @@ class Rest implements \Magento\Framework\App\FrontControllerInterface
3533
*
3634
* @deprecated 100.3.0
3735
*/
38-
const SCHEMA_PATH = '/schema';
36+
public const SCHEMA_PATH = '/schema';
3937

4038
/**
4139
* @var Router
@@ -112,6 +110,11 @@ class Rest implements \Magento\Framework\App\FrontControllerInterface
112110
*/
113111
protected $requestProcessorPool;
114112

113+
/**
114+
* @var RequestValidatorInterface
115+
*/
116+
private $requestValidator;
117+
115118
/**
116119
* @var StoreManagerInterface
117120
* @deprecated 100.1.0
@@ -134,6 +137,7 @@ class Rest implements \Magento\Framework\App\FrontControllerInterface
134137
* @param ParamsOverrider $paramsOverrider
135138
* @param StoreManagerInterface $storeManager
136139
* @param RequestProcessorPool $requestProcessorPool
140+
* @param RequestValidatorInterface $requestValidator
137141
*
138142
* TODO: Consider removal of warning suppression
139143
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
@@ -151,7 +155,8 @@ public function __construct(
151155
\Magento\Framework\App\AreaList $areaList,
152156
ParamsOverrider $paramsOverrider,
153157
StoreManagerInterface $storeManager,
154-
RequestProcessorPool $requestProcessorPool
158+
RequestProcessorPool $requestProcessorPool,
159+
RequestValidatorInterface $requestValidator
155160
) {
156161
$this->_router = $router;
157162
$this->_request = $request;
@@ -166,6 +171,7 @@ public function __construct(
166171
$this->paramsOverrider = $paramsOverrider;
167172
$this->storeManager = $storeManager;
168173
$this->requestProcessorPool = $requestProcessorPool;
174+
$this->requestValidator = $requestValidator;
169175
}
170176

171177
/**
@@ -184,6 +190,7 @@ public function dispatch(\Magento\Framework\App\RequestInterface $request)
184190
$this->areaList->getArea($this->_appState->getAreaCode())
185191
->load(\Magento\Framework\App\Area::PART_TRANSLATE);
186192
try {
193+
$this->requestValidator->validate($this->_request);
187194
$processor = $this->requestProcessorPool->getProcessor($this->_request);
188195
$processor->process($this->_request);
189196
} catch (\Exception $e) {

app/code/Magento/Webapi/etc/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
-->
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
99
<preference for="Magento\Webapi\Model\ConfigInterface" type="Magento\Webapi\Model\Config" />
10+
<preference for="Magento\Framework\Webapi\Rest\RequestValidatorInterface" type="Magento\Framework\Webapi\Rest\RequestMethodValidator" />
1011
<type name="Magento\Framework\App\AreaList">
1112
<arguments>
1213
<argument name="areas" xsi:type="array">
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Webapi\Controller;
9+
10+
use Magento\Framework\Webapi\Exception as WebapiException;
11+
use Magento\Framework\Webapi\Rest\Request;
12+
use Magento\Framework\Webapi\Rest\Response;
13+
use Magento\TestFramework\Helper\Bootstrap;
14+
use PHPUnit\Framework\TestCase;
15+
16+
class RestTest extends TestCase
17+
{
18+
/**
19+
* @var Request
20+
*/
21+
private $request;
22+
23+
/**
24+
* @var Response
25+
*/
26+
private $response;
27+
28+
/**
29+
* @var Rest
30+
*/
31+
private $controller;
32+
33+
protected function setUp(): void
34+
{
35+
$this->request = Bootstrap::getObjectManager()->create(Request::class);
36+
$this->response = Bootstrap::getObjectManager()->create(Response::class);
37+
$this->controller = Bootstrap::getObjectManager()->create(
38+
Rest::class,
39+
[
40+
'request' => $this->request,
41+
'response' => $this->response,
42+
]
43+
);
44+
}
45+
46+
public function testDispatchUnsupportedMethod(): void
47+
{
48+
$this->request->setMethod('OPTIONS');
49+
$this->controller->dispatch($this->request);
50+
self::assertTrue($this->response->isException());
51+
/** @var WebapiException $exception */
52+
$exception = $this->response->getException()[0];
53+
self::assertInstanceOf(WebapiException::class, $exception);
54+
self::assertEquals(405, $exception->getHttpCode());
55+
}
56+
}

lib/internal/Magento/Framework/Webapi/Rest/Request.php

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<?php
22
/**
3-
* REST API request.
4-
*
53
* Copyright © Magento, Inc. All rights reserved.
64
* See COPYING.txt for license details.
75
*/
@@ -11,23 +9,28 @@
119
use Magento\Framework\Api\SimpleDataObjectConverter;
1210
use Magento\Framework\Phrase;
1311

12+
/**
13+
* REST API request.
14+
*
15+
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
16+
*/
1417
class Request extends \Magento\Framework\Webapi\Request
1518
{
1619
/**#@+
1720
* HTTP methods supported by REST.
1821
*/
19-
const HTTP_METHOD_GET = 'GET';
20-
const HTTP_METHOD_DELETE = 'DELETE';
21-
const HTTP_METHOD_PUT = 'PUT';
22-
const HTTP_METHOD_POST = 'POST';
22+
public const HTTP_METHOD_GET = 'GET';
23+
public const HTTP_METHOD_DELETE = 'DELETE';
24+
public const HTTP_METHOD_PUT = 'PUT';
25+
public const HTTP_METHOD_POST = 'POST';
2326
/**#@-*/
2427

2528
/**
2629
* Character set which must be used in request.
2730
*/
28-
const REQUEST_CHARSET = 'utf-8';
31+
public const REQUEST_CHARSET = 'utf-8';
2932

30-
const DEFAULT_ACCEPT = '*/*';
33+
public const DEFAULT_ACCEPT = '*/*';
3134

3235
/**
3336
* @var string
@@ -192,7 +195,7 @@ public function getRequestData()
192195
$requestBodyParams = [];
193196
$params = $this->getParams();
194197

195-
$httpMethod = $this->getHttpMethod();
198+
$httpMethod = $this->getMethod();
196199
if ($httpMethod == self::HTTP_METHOD_POST ||
197200
$httpMethod == self::HTTP_METHOD_PUT
198201
) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Webapi\Rest;
9+
10+
use Magento\Framework\Exception\InputException;
11+
use Magento\Framework\Webapi\Exception as WebapiException;
12+
13+
/**
14+
* Validator of supported HTTP methods.
15+
*/
16+
class RequestMethodValidator implements RequestValidatorInterface
17+
{
18+
/**
19+
* @inheritdoc
20+
*/
21+
public function validate(Request $request): void
22+
{
23+
try {
24+
$request->getHttpMethod();
25+
} catch (InputException $e) {
26+
throw new WebapiException(
27+
__('The %1 HTTP method is not supported.', $request->getMethod()),
28+
0,
29+
WebapiException::HTTP_METHOD_NOT_ALLOWED
30+
);
31+
}
32+
}
33+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Webapi\Rest;
9+
10+
use Magento\Framework\Webapi\Exception as WebapiException;
11+
12+
/**
13+
* Interface for validating REST requests.
14+
*/
15+
interface RequestValidatorInterface
16+
{
17+
/**
18+
* Validate provided request.
19+
*
20+
* @param Request $request
21+
* @return void
22+
* @throws WebapiException
23+
*/
24+
public function validate(Request $request): void;
25+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Webapi\Test\Unit\Rest;
9+
10+
use Magento\Framework\Exception\InputException;
11+
use Magento\Framework\Webapi\Exception as WebapiException;
12+
use Magento\Framework\Webapi\Rest\Request;
13+
use Magento\Framework\Webapi\Rest\RequestMethodValidator;
14+
use PHPUnit\Framework\TestCase;
15+
16+
class RequestMethodValidatorTest extends TestCase
17+
{
18+
protected function setUp(): void
19+
{
20+
$this->validator = new RequestMethodValidator();
21+
}
22+
23+
public function testValidate(): void
24+
{
25+
$requestMock = $this->createMock(Request::class);
26+
$requestMock->expects(self::once())
27+
->method('getHttpMethod')
28+
->willReturn('POST');
29+
$this->validator->validate($requestMock);
30+
}
31+
32+
public function testValidateWithException(): void
33+
{
34+
$this->expectException(WebapiException::class);
35+
$this->expectExceptionMessage('The OPTIONS HTTP method is not supported.');
36+
37+
$exceptionMock = $this->createMock(InputException::class);
38+
$requestMock = $this->createMock(Request::class);
39+
$requestMock->expects(self::once())
40+
->method('getHttpMethod')
41+
->willThrowException($exceptionMock);
42+
$requestMock->expects(self::once())
43+
->method('getMethod')
44+
->willReturn('OPTIONS');
45+
$this->validator->validate($requestMock);
46+
}
47+
}

0 commit comments

Comments
 (0)