From e519889449ab94baa004f09d2fb655ab9448f285 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 6 Oct 2021 16:25:38 -0500 Subject: [PATCH 01/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Controller/Rest/InputParamsResolver.php | 15 +++- .../Webapi/Controller/Rest/Router/Route.php | 33 +++++++- .../Controller/Soap/Request/Handler.php | 15 +++- .../Magento/Webapi/Model/Config/Converter.php | 32 +++++++ app/code/Magento/Webapi/Model/Rest/Config.php | 9 +- .../Magento/Webapi/Model/ServiceMetadata.php | 10 ++- app/code/Magento/Webapi/Model/Soap/Config.php | 12 ++- app/code/Magento/Webapi/etc/webapi_base.xsd | 3 +- .../Magento/WebapiAsync/etc/webapi_async.xsd | 7 ++ .../Config/ConfigOptionsListConstants.php | 10 +++ .../Webapi/Validator/EntityArrayValidator.php | 83 ++++++++++++++++++- .../InputArraySizeLimitValue.php | 40 +++++++++ 12 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php index b5588a9a49a2f..b50c51ffbe4fe 100644 --- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php +++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Webapi\Controller\Rest; use Magento\Framework\Api\SimpleDataObjectConverter; @@ -12,6 +14,7 @@ use Magento\Framework\Webapi\Exception; use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Rest\Request as RestRequest; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\Router\Route; /** @@ -54,6 +57,11 @@ class InputParamsResolver */ private $methodsMap; + /** + * @var InputArraySizeLimitValue + */ + private $inputArraySizeLimitValue; + /** * Initialize dependencies * @@ -63,6 +71,7 @@ class InputParamsResolver * @param Router $router * @param RequestValidator $requestValidator * @param MethodsMap|null $methodsMap + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( RestRequest $request, @@ -70,7 +79,8 @@ public function __construct( ServiceInputProcessor $serviceInputProcessor, Router $router, RequestValidator $requestValidator, - MethodsMap $methodsMap = null + MethodsMap $methodsMap = null, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; @@ -79,6 +89,8 @@ public function __construct( $this->requestValidator = $requestValidator; $this->methodsMap = $methodsMap ?: ObjectManager::getInstance() ->get(MethodsMap::class); + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?: ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); } /** @@ -94,6 +106,7 @@ public function resolve() $serviceMethodName = $route->getServiceMethod(); $serviceClassName = $route->getServiceClass(); $inputData = $this->getInputData(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); return $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); } diff --git a/app/code/Magento/Webapi/Controller/Rest/Router/Route.php b/app/code/Magento/Webapi/Controller/Rest/Router/Route.php index ca5dfe2e2b822..d346e1cb46951 100644 --- a/app/code/Magento/Webapi/Controller/Rest/Router/Route.php +++ b/app/code/Magento/Webapi/Controller/Rest/Router/Route.php @@ -1,15 +1,19 @@ route; } + + /** + * Get array size limit of input data + * + * @return int|null + */ + public function getInputArraySizeLimit(): ?int + { + return $this->inputArraySizeLimit; + } + + /** + * Set array size limit of input data + * + * @param int|null $limit + */ + public function setInputArraySizeLimit(?int $limit): void + { + $this->inputArraySizeLimit = $limit; + } } diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index a01c5054f9b5f..ab450895b214c 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Webapi\Controller\Soap\Request; use Magento\Framework\Api\ExtensibleDataInterface; @@ -16,6 +18,7 @@ use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Request as SoapRequest; use Magento\Framework\Webapi\Exception as WebapiException; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Model\Soap\Config as SoapConfig; use Magento\Framework\Reflection\MethodsMap; @@ -77,6 +80,11 @@ class Handler */ private $paramsOverrider; + /** + * @var InputArraySizeLimitValue|null + */ + private $inputArraySizeLimitValue; + /** * Initialize dependencies. * @@ -89,6 +97,7 @@ class Handler * @param DataObjectProcessor $dataObjectProcessor * @param MethodsMap $methodsMapProcessor * @param ParamsOverrider|null $paramsOverrider + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( SoapRequest $request, @@ -99,7 +108,8 @@ public function __construct( ServiceInputProcessor $serviceInputProcessor, DataObjectProcessor $dataObjectProcessor, MethodsMap $methodsMapProcessor, - ?ParamsOverrider $paramsOverrider = null + ?ParamsOverrider $paramsOverrider = null, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->_request = $request; $this->_objectManager = $objectManager; @@ -110,6 +120,8 @@ public function __construct( $this->_dataObjectProcessor = $dataObjectProcessor; $this->methodsMapProcessor = $methodsMapProcessor; $this->paramsOverrider = $paramsOverrider ?? ObjectManager::getInstance()->get(ParamsOverrider::class); + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); } /** @@ -164,6 +176,7 @@ private function prepareOperationInput(string $serviceClass, array $methodMetada $arguments = reset($arguments); $arguments = $this->_dataObjectConverter->convertStdObjectToArray($arguments, true); $arguments = $this->paramsOverrider->override($arguments, $methodMetadata[ServiceMetadata::KEY_ROUTE_PARAMS]); + $this->inputArraySizeLimitValue->set($methodMetadata[ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT]); return $this->serviceInputProcessor->process( $serviceClass, diff --git a/app/code/Magento/Webapi/Model/Config/Converter.php b/app/code/Magento/Webapi/Model/Config/Converter.php index 837a0f84423ad..2a8274a0b2903 100644 --- a/app/code/Magento/Webapi/Model/Config/Converter.php +++ b/app/code/Magento/Webapi/Model/Config/Converter.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Webapi\Model\Config; /** @@ -29,6 +32,7 @@ class Converter implements \Magento\Framework\Config\ConverterInterface const KEY_METHODS = 'methods'; const KEY_DESCRIPTION = 'description'; const KEY_REAL_SERVICE_METHOD = 'realMethod'; + const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /**#@-*/ /** @@ -95,6 +99,8 @@ public function convert($source) $secureNode = $route->attributes->getNamedItem('secure'); $secure = $secureNode ? (bool)trim($secureNode->nodeValue) : false; + $arraySizeLimit = $this->getInputArraySizeLimit($route); + // We could handle merging here by checking if the route already exists $result[self::KEY_ROUTES][$url][$method] = [ self::KEY_SECURE => $secure, @@ -104,6 +110,7 @@ public function convert($source) ], self::KEY_ACL_RESOURCES => $resourceReferences, self::KEY_DATA_PARAMETERS => $data, + self::KEY_INPUT_ARRAY_SIZE_LIMIT => $arraySizeLimit, ]; $serviceSecure = false; @@ -113,6 +120,9 @@ public function convert($source) if (!isset($serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_REAL_SERVICE_METHOD])) { $serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_REAL_SERVICE_METHOD] = $serviceMethod; } + if (!isset($serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_INPUT_ARRAY_SIZE_LIMIT])) { + $serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_INPUT_ARRAY_SIZE_LIMIT] = $arraySizeLimit; + } $serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_SECURE] = $serviceSecure || $secure; $serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_DATA_PARAMETERS] = $serviceData; @@ -168,4 +178,26 @@ protected function convertVersion($url) { return substr($url, 1, strpos($url, '/', 1)-1); } + + /** + * Returns array size limit of input data + * + * @param \DOMElement $routeDOMElement + * @return int|null + */ + private function getInputArraySizeLimit(\DOMElement $routeDOMElement): ?int + { + /** @var \DOMElement $dataDOMElement */ + foreach ($routeDOMElement->getElementsByTagName('data') as $dataDOMElement) { + if ($dataDOMElement->nodeType === XML_ELEMENT_NODE) { + $inputArraySizeLimitDOMNode = $dataDOMElement->attributes + ->getNamedItem(self::KEY_INPUT_ARRAY_SIZE_LIMIT); + return ($inputArraySizeLimitDOMNode instanceof \DOMNode) + ? (int)$inputArraySizeLimitDOMNode->nodeValue + : null; + } + } + + return null; + } } diff --git a/app/code/Magento/Webapi/Model/Rest/Config.php b/app/code/Magento/Webapi/Model/Rest/Config.php index d572b9c21cfc9..6ac1d262c77e3 100644 --- a/app/code/Magento/Webapi/Model/Rest/Config.php +++ b/app/code/Magento/Webapi/Model/Rest/Config.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Webapi\Model\Rest; use Magento\Webapi\Controller\Rest\Router\Route; @@ -33,6 +36,7 @@ class Config const KEY_ROUTE_PATH = 'routePath'; const KEY_ACL_RESOURCES = 'resources'; const KEY_PARAMETERS = 'parameters'; + const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /*#@-*/ /*#@-*/ @@ -79,7 +83,8 @@ protected function _createRoute($routeData) ->setServiceMethod($routeData[self::KEY_METHOD]) ->setSecure($routeData[self::KEY_IS_SECURE]) ->setAclResources($routeData[self::KEY_ACL_RESOURCES]) - ->setParameters($routeData[self::KEY_PARAMETERS]); + ->setParameters($routeData[self::KEY_PARAMETERS]) + ->setInputArraySizeLimit($routeData[self::KEY_INPUT_ARRAY_SIZE_LIMIT]); return $route; } @@ -120,6 +125,7 @@ public function getRestRoutes(\Magento\Framework\Webapi\Rest\Request $request) self::KEY_IS_SECURE => $methodInfo[Converter::KEY_SECURE], self::KEY_ACL_RESOURCES => array_keys($methodInfo[Converter::KEY_ACL_RESOURCES]), self::KEY_PARAMETERS => $methodInfo[Converter::KEY_DATA_PARAMETERS], + self::KEY_INPUT_ARRAY_SIZE_LIMIT => $methodInfo[Converter::KEY_INPUT_ARRAY_SIZE_LIMIT], ] ); return $routes; @@ -143,6 +149,7 @@ public function getRestRoutes(\Magento\Framework\Webapi\Rest\Request $request) self::KEY_IS_SECURE => $methodInfo[Converter::KEY_SECURE], self::KEY_ACL_RESOURCES => $aclResources, self::KEY_PARAMETERS => $methodInfo[Converter::KEY_DATA_PARAMETERS], + self::KEY_INPUT_ARRAY_SIZE_LIMIT => $methodInfo[Converter::KEY_INPUT_ARRAY_SIZE_LIMIT], ] ); } diff --git a/app/code/Magento/Webapi/Model/ServiceMetadata.php b/app/code/Magento/Webapi/Model/ServiceMetadata.php index 36f5819b03c98..c948dd824aa3b 100644 --- a/app/code/Magento/Webapi/Model/ServiceMetadata.php +++ b/app/code/Magento/Webapi/Model/ServiceMetadata.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Webapi\Model; use Magento\Framework\App\ObjectManager; @@ -38,6 +41,8 @@ class ServiceMetadata const KEY_METHOD_ALIAS = 'methodAlias'; + const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; + const SERVICES_CONFIG_CACHE_ID = 'services-services-config'; const ROUTES_CONFIG_CACHE_ID = 'routes-services-config'; @@ -123,7 +128,8 @@ protected function initServicesMetadata() self::KEY_IS_SECURE => $methodMetadata[Converter::KEY_SECURE], self::KEY_ACL_RESOURCES => $methodMetadata[Converter::KEY_ACL_RESOURCES], self::KEY_METHOD_ALIAS => $methodName, - self::KEY_ROUTE_PARAMS => $methodMetadata[Converter::KEY_DATA_PARAMETERS] + self::KEY_ROUTE_PARAMS => $methodMetadata[Converter::KEY_DATA_PARAMETERS], + self::KEY_INPUT_ARRAY_SIZE_LIMIT => $methodMetadata[Converter::KEY_INPUT_ARRAY_SIZE_LIMIT], ]; $services[$serviceName][self::KEY_CLASS] = $serviceClass; $methods[] = $methodMetadata[Converter::KEY_REAL_SERVICE_METHOD]; @@ -311,9 +317,11 @@ protected function initRoutesMetadata() $version = explode('/', ltrim($url, '/'))[0]; $serviceName = $this->getServiceName($serviceClass, $version); $methodName = $data[Converter::KEY_SERVICE][Converter::KEY_METHOD]; + $limit = $data[Converter::KEY_INPUT_ARRAY_SIZE_LIMIT]; $routes[$serviceName][self::KEY_ROUTES][$url][$method][self::KEY_ROUTE_METHOD] = $methodName; $routes[$serviceName][self::KEY_ROUTES][$url][$method][self::KEY_ROUTE_PARAMS] = $data[Converter::KEY_DATA_PARAMETERS]; + $routes[$serviceName][self::KEY_ROUTES][$url][$method][self::KEY_INPUT_ARRAY_SIZE_LIMIT] = $limit; } } return $routes; diff --git a/app/code/Magento/Webapi/Model/Soap/Config.php b/app/code/Magento/Webapi/Model/Soap/Config.php index 190280ff8f004..54fa76b43cb8d 100644 --- a/app/code/Magento/Webapi/Model/Soap/Config.php +++ b/app/code/Magento/Webapi/Model/Soap/Config.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Webapi\Model\Soap; use Magento\Webapi\Model\ServiceMetadata; @@ -77,12 +80,14 @@ protected function getSoapOperations($requestedServices) $class = $serviceData[ServiceMetadata::KEY_CLASS]; $operation = $methodData[ServiceMetadata::KEY_METHOD_ALIAS]; $operationName = $serviceName . ucfirst($operation); + $inputArraySizeLimit = $methodData[ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT]; $this->soapOperations[$operationName] = [ ServiceMetadata::KEY_CLASS => $class, ServiceMetadata::KEY_METHOD => $method, ServiceMetadata::KEY_IS_SECURE => $methodData[ServiceMetadata::KEY_IS_SECURE], ServiceMetadata::KEY_ACL_RESOURCES => $methodData[ServiceMetadata::KEY_ACL_RESOURCES], - ServiceMetadata::KEY_ROUTE_PARAMS => $methodData[ServiceMetadata::KEY_ROUTE_PARAMS] + ServiceMetadata::KEY_ROUTE_PARAMS => $methodData[ServiceMetadata::KEY_ROUTE_PARAMS], + ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT => $inputArraySizeLimit, ]; } } @@ -108,12 +113,15 @@ public function getServiceMethodInfo($soapOperation, $requestedServices) \Magento\Framework\Webapi\Exception::HTTP_NOT_FOUND ); } + $inputArraySizeLimit = $soapOperations[$soapOperation][ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT]; + return [ ServiceMetadata::KEY_CLASS => $soapOperations[$soapOperation][ServiceMetadata::KEY_CLASS], ServiceMetadata::KEY_METHOD => $soapOperations[$soapOperation][ServiceMetadata::KEY_METHOD], ServiceMetadata::KEY_IS_SECURE => $soapOperations[$soapOperation][ServiceMetadata::KEY_IS_SECURE], ServiceMetadata::KEY_ACL_RESOURCES => $soapOperations[$soapOperation][ServiceMetadata::KEY_ACL_RESOURCES], - ServiceMetadata::KEY_ROUTE_PARAMS => $soapOperations[$soapOperation][ServiceMetadata::KEY_ROUTE_PARAMS] + ServiceMetadata::KEY_ROUTE_PARAMS => $soapOperations[$soapOperation][ServiceMetadata::KEY_ROUTE_PARAMS], + ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT => $inputArraySizeLimit, ]; } diff --git a/app/code/Magento/Webapi/etc/webapi_base.xsd b/app/code/Magento/Webapi/etc/webapi_base.xsd index 7d1a5a14ba78f..77ef3f78324c8 100644 --- a/app/code/Magento/Webapi/etc/webapi_base.xsd +++ b/app/code/Magento/Webapi/etc/webapi_base.xsd @@ -55,8 +55,9 @@ - + + diff --git a/app/code/Magento/WebapiAsync/etc/webapi_async.xsd b/app/code/Magento/WebapiAsync/etc/webapi_async.xsd index c41d9811f2d63..7c175a0f54df7 100644 --- a/app/code/Magento/WebapiAsync/etc/webapi_async.xsd +++ b/app/code/Magento/WebapiAsync/etc/webapi_async.xsd @@ -26,6 +26,9 @@ + + + @@ -40,4 +43,8 @@ + + + + diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index 96999187bd6cb..8b9a2bdba93c7 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -53,6 +53,16 @@ class ConfigOptionsListConstants */ const CONFIG_PATH_FORCE_HTML_MINIFICATION = 'force_html_minification'; + /** + * Default limiting input array size for synchronous Web API + */ + const CONFIG_PATH_WEBAPI_SYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT = 'webapi/sync/default_input_array_size_limit'; + + /** + * Default limiting input array size for asynchronous Web API + */ + const CONFIG_PATH_WEBAPI_ASYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT = 'webapi/async/default_input_array_size_limit'; + /**#@+ * Input keys for the options */ diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php index 5502179e495be..6177e6d4e1157 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php @@ -8,36 +8,79 @@ namespace Magento\Framework\Webapi\Validator; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Config\ConfigOptionsListConstants as ConfigConstants; +use Magento\Framework\App\Request\Http as Request; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\InvalidArgumentException; +use Magento\Framework\Exception\RuntimeException; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; +use Magento\Framework\App\DeploymentConfig; /** * Validates service input */ class EntityArrayValidator implements ServiceInputValidatorInterface { + private const KEY_ASYNC = 'async'; + + /** + * Default limit for asynchronous request + */ + private const DEFAULT_ASYNC_INPUT_ARRAY_SIZE_LIMIT = 5000; + /** * @var int */ private $complexArrayItemLimit; + /** + * @var InputArraySizeLimitValue + */ + private $inputArraySizeLimitValue; + + /** + * @var Request + */ + private $request; + + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + /** * @param int $complexArrayItemLimit + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue + * @param Request|null $request + * @param DeploymentConfig|null $deploymentConfig */ - public function __construct(int $complexArrayItemLimit) - { + public function __construct( + int $complexArrayItemLimit, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, + ?Request $request = null, + ?DeploymentConfig $deploymentConfig = null + ) { $this->complexArrayItemLimit = $complexArrayItemLimit; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); + $this->request = $request ?? ObjectManager::getInstance()->get(Request::class); + $this->deploymentConfig = $deploymentConfig ?? ObjectManager::getInstance()->get(DeploymentConfig::class); } /** * @inheritDoc + * @throws FileSystemException + * @throws RuntimeException */ public function validateComplexArrayType(string $className, array $items): void { - if (count($items) > $this->complexArrayItemLimit) { + $limit = $this->getLimit(); + if (count($items) > $limit) { throw new InvalidArgumentException( __( 'Maximum items of type "%type" is %max', - ['type' => $className, 'max' => $this->complexArrayItemLimit] + ['type' => $className, 'max' => $limit] ) ); } @@ -50,4 +93,36 @@ public function validateComplexArrayType(string $className, array $items): void public function validateEntityValue(object $entity, string $propertyName, $value): void { } + + /** + * Returns true if using asynchronous Webapi + * + * @return bool + */ + private function isAsync(): bool + { + $requestUriByPart = explode('/', trim($this->request->getPathInfo(), '/')); + return isset($requestUriByPart[2]) && self::KEY_ASYNC === $requestUriByPart[2]; + } + + /** + * Returns limit + * + * @return int + * @throws FileSystemException + * @throws RuntimeException + */ + private function getLimit(): int + { + return $this->inputArraySizeLimitValue->get() ?? ($this->isAsync() + ? $this->deploymentConfig->get( + ConfigConstants::CONFIG_PATH_WEBAPI_ASYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT, + self::DEFAULT_ASYNC_INPUT_ARRAY_SIZE_LIMIT + ) + : $this->deploymentConfig->get( + ConfigConstants::CONFIG_PATH_WEBAPI_SYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT, + $this->complexArrayItemLimit + ) + ); + } } diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php new file mode 100644 index 0000000000000..a57365ff13852 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php @@ -0,0 +1,40 @@ +value = $value; + } + + /** + * Get value of input array size limit + * + * @return int|null + */ + public function get(): ?int + { + return $this->value; + } +} From bb9b6c11bb98f80af2fed4b8d395e4e4ca2a4bd8 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Thu, 7 Oct 2021 00:42:31 -0500 Subject: [PATCH 02/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Rest/Asynchronous/InputParamsResolver.php | 36 ++++++-- .../Model/ServiceConfig/Converter.php | 25 ++++++ .../WebapiAsync/Plugin/Rest/Config.php | 85 +++++++++++++++++++ .../WebapiAsync/etc/webapi_rest/di.xml | 5 +- .../Webapi/Validator/EntityArrayValidator.php | 7 +- 5 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 app/code/Magento/WebapiAsync/Plugin/Rest/Config.php diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index 064bd99b9b6bf..18ffc273fac84 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,8 +8,10 @@ namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Controller\Rest\RequestValidator; @@ -49,6 +51,11 @@ class InputParamsResolver */ private $isBulk; + /** + * @var InputArraySizeLimitValue|null + */ + private $inputArraySizeLimitValue; + /** * Initialize dependencies. * @@ -59,6 +66,7 @@ class InputParamsResolver * @param \Magento\Webapi\Controller\Rest\RequestValidator $requestValidator * @param \Magento\Webapi\Controller\Rest\InputParamsResolver $inputParamsResolver * @param bool $isBulk + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( RestRequest $request, @@ -67,7 +75,8 @@ public function __construct( Router $router, RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, - $isBulk = false + $isBulk = false, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; @@ -76,6 +85,9 @@ public function __construct( $this->requestValidator = $requestValidator; $this->inputParamsResolver = $inputParamsResolver; $this->isBulk = $isBulk; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); + } /** @@ -96,8 +108,17 @@ public function resolve() } $this->requestValidator->validate(); $webapiResolvedParams = []; + $route = $this->getRoute(); + $serviceMethodName = $route->getServiceMethod(); + $serviceClassName = $route->getServiceClass(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); + foreach ($this->getInputData() as $key => $singleEntityParams) { - $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams); + $webapiResolvedParams[$key] = $this->resolveBulkItemParams( + $singleEntityParams, + $serviceMethodName, + $serviceClassName + ); } return $webapiResolvedParams; } @@ -142,17 +163,14 @@ public function getRoute() * we don't need to merge body params with url params and use only body params * * @param array $inputData data to send to method in key-value format + * @param string $serviceMethodName service method name + * @param string $serviceClassName service class name * @return array list of parameters that can be used to call the service method * @throws \Magento\Framework\Exception\InputException if no value is provided for required parameters * @throws \Magento\Framework\Webapi\Exception */ - private function resolveBulkItemParams($inputData) + private function resolveBulkItemParams(array $inputData, string $serviceMethodName, string $serviceClassName) { - $route = $this->getRoute(); - $serviceMethodName = $route->getServiceMethod(); - $serviceClassName = $route->getServiceClass(); - $inputParams = $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); - - return $inputParams; + return $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); } } diff --git a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php index 2c85796a3ab19..359bd9d1a2820 100644 --- a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php +++ b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php @@ -21,6 +21,7 @@ class Converter implements \Magento\Framework\Config\ConverterInterface const KEY_METHODS = 'methods'; const KEY_SYNCHRONOUS_INVOCATION_ONLY = 'synchronousInvocationOnly'; const KEY_ROUTES = 'routes'; + const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /**#@-*/ private $allowedRouteMethods = [ @@ -183,11 +184,13 @@ private function convertRouteCustomizations($source) $routeUrl = $this->getRouteUrl($route); $routeMethod = $this->getRouteMethod($route); $routeAlias = $this->getRouteAlias($route); + $inputArraySizeLimit =$this->getInputArraySizeLimit($route); if ($routeUrl && $routeMethod && $routeAlias) { if (!isset($customRoutes[$routeAlias])) { $customRoutes[$routeAlias] = []; } $customRoutes[$routeAlias][$routeMethod] = $routeUrl; + $customRoutes[$routeAlias][self::KEY_INPUT_ARRAY_SIZE_LIMIT] = $inputArraySizeLimit; } } return $customRoutes; @@ -232,4 +235,26 @@ private function validateRouteMethod($method) { return in_array($method, $this->allowedRouteMethods); } + + /** + * Returns array size limit of input data + * + * @param \DOMElement $routeDOMElement + * @return int|null + */ + private function getInputArraySizeLimit(\DOMElement $routeDOMElement): ?int + { + /** @var \DOMElement $dataDOMElement */ + foreach ($routeDOMElement->getElementsByTagName('data') as $dataDOMElement) { + if ($dataDOMElement->nodeType === XML_ELEMENT_NODE) { + $inputArraySizeLimitDOMNode = $dataDOMElement->attributes + ->getNamedItem(self::KEY_INPUT_ARRAY_SIZE_LIMIT); + return ($inputArraySizeLimitDOMNode instanceof \DOMNode) + ? (int)$inputArraySizeLimitDOMNode->nodeValue + : null; + } + } + + return null; + } } diff --git a/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php b/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php new file mode 100644 index 0000000000000..d163bc310649a --- /dev/null +++ b/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php @@ -0,0 +1,85 @@ +serviceConfig = $serviceConfig; + } + + /** + * Overrides the rules for an asynchronous request + * + * @param RestConfig $restConfig + * @param array $routes + * @param Request $request + * @return Route[] + * @throws InputException + */ + public function afterGetRestRoutes(RestConfig $restConfig, array $routes, Request $request): array + { + $httpMethod = $request->getHttpMethod(); + if ($httpMethod === Request::HTTP_METHOD_GET || !$this->canProcess($request)) { + return $routes; + } + + $routeConfigs = $this->serviceConfig->getServices()[self::KEY_ROUTES] ?? []; + + /** @var Route $route */ + foreach ($routes as $route) { + $inputArraySizeLimit = null; + foreach ($routeConfigs as $routeConfig) { + if (!isset($routeConfig[$httpMethod]) + || false === strpos($routeConfig[$httpMethod], $route->getRoutePath()) + || !isset($routeConfig[RestConfig::KEY_INPUT_ARRAY_SIZE_LIMIT])) { + continue; + } + $inputArraySizeLimit = $routeConfig[RestConfig::KEY_INPUT_ARRAY_SIZE_LIMIT]; + break; + } + $route->setInputArraySizeLimit($inputArraySizeLimit); + } + + return $routes; + } + + /** + * Allow the process if using the asynchronous Webapi + * + * @param Request $request + * @return bool + */ + private function canProcess(Request $request): bool + { + return preg_match(self::ASYNC_PROCESSOR_PATH, $request->getUri()->getPath()) === 1; + } +} diff --git a/app/code/Magento/WebapiAsync/etc/webapi_rest/di.xml b/app/code/Magento/WebapiAsync/etc/webapi_rest/di.xml index 107754df10db6..e03bea7987396 100644 --- a/app/code/Magento/WebapiAsync/etc/webapi_rest/di.xml +++ b/app/code/Magento/WebapiAsync/etc/webapi_rest/di.xml @@ -17,4 +17,7 @@ - \ No newline at end of file + + + + diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php index 6177e6d4e1157..6cbf2e3502cd1 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php @@ -10,7 +10,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Config\ConfigOptionsListConstants as ConfigConstants; -use Magento\Framework\App\Request\Http as Request; +use Magento\Framework\Webapi\Request; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\InvalidArgumentException; use Magento\Framework\Exception\RuntimeException; @@ -22,7 +22,7 @@ */ class EntityArrayValidator implements ServiceInputValidatorInterface { - private const KEY_ASYNC = 'async'; + private const ASYNC_PROCESSOR_PATH = "/\/async\/V\d\//"; /** * Default limit for asynchronous request @@ -101,8 +101,7 @@ public function validateEntityValue(object $entity, string $propertyName, $value */ private function isAsync(): bool { - $requestUriByPart = explode('/', trim($this->request->getPathInfo(), '/')); - return isset($requestUriByPart[2]) && self::KEY_ASYNC === $requestUriByPart[2]; + return preg_match(self::ASYNC_PROCESSOR_PATH, $this->request->getPathInfo()) === 1; } /** From eab49c5a41bd3fa373575aa00836dab7f3e85a26 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Sun, 10 Oct 2021 19:22:06 -0500 Subject: [PATCH 03/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Controller/Soap/Request/Handler.php | 2 +- .../Webapi/Validator/EntityArrayValidator.php | 62 +------------------ .../InputArraySizeLimitValue.php | 57 ++++++++++++++++- 3 files changed, 60 insertions(+), 61 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index ab450895b214c..c8367934a36e3 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -81,7 +81,7 @@ class Handler private $paramsOverrider; /** - * @var InputArraySizeLimitValue|null + * @var InputArraySizeLimitValue */ private $inputArraySizeLimitValue; diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php index 6cbf2e3502cd1..f8b360ad01751 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php @@ -9,26 +9,16 @@ namespace Magento\Framework\Webapi\Validator; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Config\ConfigOptionsListConstants as ConfigConstants; -use Magento\Framework\Webapi\Request; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\InvalidArgumentException; use Magento\Framework\Exception\RuntimeException; use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; -use Magento\Framework\App\DeploymentConfig; /** * Validates service input */ class EntityArrayValidator implements ServiceInputValidatorInterface { - private const ASYNC_PROCESSOR_PATH = "/\/async\/V\d\//"; - - /** - * Default limit for asynchronous request - */ - private const DEFAULT_ASYNC_INPUT_ARRAY_SIZE_LIMIT = 5000; - /** * @var int */ @@ -39,43 +29,28 @@ class EntityArrayValidator implements ServiceInputValidatorInterface */ private $inputArraySizeLimitValue; - /** - * @var Request - */ - private $request; - - /** - * @var DeploymentConfig - */ - private $deploymentConfig; - /** * @param int $complexArrayItemLimit * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue - * @param Request|null $request - * @param DeploymentConfig|null $deploymentConfig */ public function __construct( int $complexArrayItemLimit, - ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, - ?Request $request = null, - ?DeploymentConfig $deploymentConfig = null + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->complexArrayItemLimit = $complexArrayItemLimit; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); - $this->request = $request ?? ObjectManager::getInstance()->get(Request::class); - $this->deploymentConfig = $deploymentConfig ?? ObjectManager::getInstance()->get(DeploymentConfig::class); } /** * @inheritDoc + * * @throws FileSystemException * @throws RuntimeException */ public function validateComplexArrayType(string $className, array $items): void { - $limit = $this->getLimit(); + $limit = $this->inputArraySizeLimitValue->get() ?? $this->complexArrayItemLimit; if (count($items) > $limit) { throw new InvalidArgumentException( __( @@ -93,35 +68,4 @@ public function validateComplexArrayType(string $className, array $items): void public function validateEntityValue(object $entity, string $propertyName, $value): void { } - - /** - * Returns true if using asynchronous Webapi - * - * @return bool - */ - private function isAsync(): bool - { - return preg_match(self::ASYNC_PROCESSOR_PATH, $this->request->getPathInfo()) === 1; - } - - /** - * Returns limit - * - * @return int - * @throws FileSystemException - * @throws RuntimeException - */ - private function getLimit(): int - { - return $this->inputArraySizeLimitValue->get() ?? ($this->isAsync() - ? $this->deploymentConfig->get( - ConfigConstants::CONFIG_PATH_WEBAPI_ASYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT, - self::DEFAULT_ASYNC_INPUT_ARRAY_SIZE_LIMIT - ) - : $this->deploymentConfig->get( - ConfigConstants::CONFIG_PATH_WEBAPI_SYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT, - $this->complexArrayItemLimit - ) - ); - } } diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php index a57365ff13852..ca75da4a93eed 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php @@ -8,16 +8,51 @@ namespace Magento\Framework\Webapi\Validator\EntityArrayValidator; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\Config\ConfigOptionsListConstants as ConstantList; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\RuntimeException; +use Magento\Framework\Webapi\Request; + /** * Value of the size limit of the input array for service input validator */ class InputArraySizeLimitValue { + private const ASYNC_PROCESSOR_PATH = "/\/async\/V\d\//"; + + /** + * Default limit for asynchronous request + */ + private const DEFAULT_ASYNC_INPUT_ARRAY_SIZE_LIMIT = 5000; + /** * @var int|null */ private $value; + /** + * @var Request + */ + private $request; + + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + + /** + * @param Request $request + * @param DeploymentConfig $deploymentConfig + */ + public function __construct( + Request $request, + DeploymentConfig $deploymentConfig + ) { + $this->request = $request; + $this->deploymentConfig = $deploymentConfig; + } + /** * Set value of input array size limit * @@ -32,9 +67,29 @@ public function set(?int $value): void * Get value of input array size limit * * @return int|null + * @throws FileSystemException + * @throws RuntimeException */ public function get(): ?int { - return $this->value; + return $this->value ?? ($this->isAsync() + ? $this->deploymentConfig->get( + ConstantList::CONFIG_PATH_WEBAPI_ASYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT, + self::DEFAULT_ASYNC_INPUT_ARRAY_SIZE_LIMIT + ) + : $this->deploymentConfig->get( + ConstantList::CONFIG_PATH_WEBAPI_SYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT + ) + ); + } + + /** + * Returns true if using asynchronous Webapi + * + * @return bool + */ + private function isAsync(): bool + { + return preg_match(self::ASYNC_PROCESSOR_PATH, $this->request->getPathInfo()) === 1; } } From 70b58dfb3d31f711f1ebfe284a21743cc1920d55 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Mon, 11 Oct 2021 11:54:49 -0500 Subject: [PATCH 04/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Rest/Asynchronous/InputParamsResolver.php | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index 18ffc273fac84..f4d41aa2e5230 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -9,6 +9,9 @@ namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\AuthorizationException; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Webapi\Exception; use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; @@ -16,6 +19,7 @@ use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Controller\Rest\RequestValidator; use Magento\Webapi\Controller\Rest\Router; +use Magento\Webapi\Controller\Rest\Router\Route; /** * This class is responsible for retrieving resolved input data @@ -43,7 +47,7 @@ class InputParamsResolver */ private $requestValidator; /** - * @var \Magento\Webapi\Controller\Rest\InputParamsResolver + * @var WebapiInputParamsResolver */ private $inputParamsResolver; /** @@ -59,12 +63,12 @@ class InputParamsResolver /** * Initialize dependencies. * - * @param \Magento\Framework\Webapi\Rest\Request $request - * @param \Magento\Webapi\Controller\Rest\ParamsOverrider $paramsOverrider - * @param \Magento\Framework\Webapi\ServiceInputProcessor $inputProcessor - * @param \Magento\Webapi\Controller\Rest\Router $router - * @param \Magento\Webapi\Controller\Rest\RequestValidator $requestValidator - * @param \Magento\Webapi\Controller\Rest\InputParamsResolver $inputParamsResolver + * @param RestRequest $request + * @param ParamsOverrider $paramsOverrider + * @param ServiceInputProcessor $inputProcessor + * @param Router $router + * @param RequestValidator $requestValidator + * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ @@ -97,9 +101,9 @@ public function __construct( * or throw \Exception if at least one request entity params is not valid * * @return array - * @throws \Magento\Framework\Exception\InputException if no value is provided for required parameters - * @throws \Magento\Framework\Webapi\Exception - * @throws \Magento\Framework\Exception\AuthorizationException + * @throws InputException if no value is provided for required parameters + * @throws Exception + * @throws AuthorizationException */ public function resolve() { @@ -109,16 +113,10 @@ public function resolve() $this->requestValidator->validate(); $webapiResolvedParams = []; $route = $this->getRoute(); - $serviceMethodName = $route->getServiceMethod(); - $serviceClassName = $route->getServiceClass(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); foreach ($this->getInputData() as $key => $singleEntityParams) { - $webapiResolvedParams[$key] = $this->resolveBulkItemParams( - $singleEntityParams, - $serviceMethodName, - $serviceClassName - ); + $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams, $route); } return $webapiResolvedParams; } @@ -136,7 +134,7 @@ public function getInputData() $inputData = $this->request->getRequestData(); $httpMethod = $this->request->getHttpMethod(); - if ($httpMethod == \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE) { + if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { $requestBodyParams = $this->request->getBodyParams(); $inputData = array_merge($requestBodyParams, $inputData); } @@ -146,7 +144,7 @@ public function getInputData() /** * Returns route. * - * @return \Magento\Webapi\Controller\Rest\Router\Route + * @return Route */ public function getRoute() { @@ -163,14 +161,16 @@ public function getRoute() * we don't need to merge body params with url params and use only body params * * @param array $inputData data to send to method in key-value format - * @param string $serviceMethodName service method name - * @param string $serviceClassName service class name + * @param Route $route * @return array list of parameters that can be used to call the service method - * @throws \Magento\Framework\Exception\InputException if no value is provided for required parameters - * @throws \Magento\Framework\Webapi\Exception + * @throws Exception */ - private function resolveBulkItemParams(array $inputData, string $serviceMethodName, string $serviceClassName) + private function resolveBulkItemParams(array $inputData, Route $route): array { - return $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); + return $this->serviceInputProcessor->process( + $route->getServiceClass(), + $route->getServiceMethod(), + $inputData + ); } } From 93117c43ab409938b5cf884b1f0325fab2d88f16 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Tue, 9 Nov 2021 17:26:54 -0600 Subject: [PATCH 05/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Webapi/Validator/EntityArrayValidator.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php index 6a5fd18644e29..514dd4601cd40 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php @@ -23,7 +23,7 @@ class EntityArrayValidator implements ServiceInputValidatorInterface /** * @var int */ - private $complexArrayItemLimit; + private int $complexArrayItemLimit; /** * @var IOLimitConfigProvider @@ -52,9 +52,9 @@ public function __construct( } /** - * @{inheritDoc} - * @throws FileSystemException - * @throws RuntimeException + * {@inheritDoc} + * + * @throws FileSystemException|RuntimeException */ public function validateComplexArrayType(string $className, array $items): void { @@ -76,7 +76,7 @@ public function validateComplexArrayType(string $className, array $items): void } /** - * @inheritDoc + * {@inheritDoc} * phpcs:disable Magento2.CodeAnalysis.EmptyBlock */ public function validateEntityValue(object $entity, string $propertyName, $value): void From 86167578df5c6388a534bfaacc073847988ea1ab Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Tue, 9 Nov 2021 22:44:48 -0600 Subject: [PATCH 06/37] AC-465: Allow to configure input limit for RESTful endpoints --- app/code/Magento/Webapi/Model/Rest/Config.php | 4 +- .../Magento/Webapi/Model/ServiceMetadata.php | 4 +- .../Test/Unit/Model/Config/_files/webapi.php | 25 ++-- .../Test/Unit/Model/Config/_files/webapi.xml | 1 + .../Model/ServiceConfig/Converter.php | 35 +++++- .../_files/Converter/webapi_async.php | 6 +- .../_files/Converter/webapi_async.xml | 4 +- .../_files/Reader/webapi_async.php | 6 +- .../_files/Reader/webapi_async_1.xml | 4 +- .../Webapi/Model/Config/_files/webapi.php | 58 +++++---- .../Webapi/Model/Config/_files/webapiA.xml | 2 +- .../Magento/Webapi/Model/Soap/ConfigTest.php | 3 +- .../Test/Unit/ServiceInputProcessorTest.php | 14 ++- .../InputArraySizeLimitValueTest.php | 95 +++++++++++++++ .../Validator/EntityArrayValidatorTest.php | 115 +++++++++++++++--- .../Webapi/Validator/EntityArrayValidator.php | 4 +- 16 files changed, 315 insertions(+), 65 deletions(-) create mode 100644 lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidator/InputArraySizeLimitValueTest.php diff --git a/app/code/Magento/Webapi/Model/Rest/Config.php b/app/code/Magento/Webapi/Model/Rest/Config.php index 6ac1d262c77e3..cbed09eef4744 100644 --- a/app/code/Magento/Webapi/Model/Rest/Config.php +++ b/app/code/Magento/Webapi/Model/Rest/Config.php @@ -39,7 +39,9 @@ class Config const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /*#@-*/ - /*#@-*/ + /** + * @var ModelConfigInterface + */ protected $_config; /** diff --git a/app/code/Magento/Webapi/Model/ServiceMetadata.php b/app/code/Magento/Webapi/Model/ServiceMetadata.php index c948dd824aa3b..d6c56f0a46920 100644 --- a/app/code/Magento/Webapi/Model/ServiceMetadata.php +++ b/app/code/Magento/Webapi/Model/ServiceMetadata.php @@ -51,7 +51,9 @@ class ServiceMetadata /**#@-*/ - /**#@-*/ + /** + * @var array + */ protected $services; /** diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.php b/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.php index bd8fcc78953a4..02daff32d2be6 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.php +++ b/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.php @@ -17,7 +17,8 @@ ], 'secure' => false, 'realMethod' => 'getById', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], 'save' => [ 'resources' => [ @@ -25,7 +26,8 @@ ], 'secure' => false, 'realMethod' => 'save', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => 50, ], 'saveSelf' => [ 'resources' => [ @@ -39,6 +41,7 @@ 'value' => null, ], ], + 'input-array-size-limit' => null, ], 'deleteById' => [ 'resources' => [ @@ -47,7 +50,8 @@ ], 'secure' => false, 'realMethod' => 'deleteById', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], ], ], @@ -70,6 +74,7 @@ 'value' => '%customer_id%', ], ], + 'input-array-size-limit' => null, ], ], '/V1/customers/me' => [ @@ -88,6 +93,7 @@ 'value' => null, ], ], + 'input-array-size-limit' => null, ], 'PUT' => [ 'secure' => true, @@ -104,6 +110,7 @@ 'value' => null, ], ], + 'input-array-size-limit' => null, ], ], '/V1/customers' => [ @@ -116,8 +123,8 @@ 'resources' => [ 'Magento_Customer::manage' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => 50, ], ], '/V1/customers/:id' => [ @@ -130,8 +137,8 @@ 'resources' => [ 'Magento_Customer::read' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], 'DELETE' => [ 'secure' => false, @@ -143,8 +150,8 @@ 'Magento_Customer::manage' => true, 'Magento_Customer::delete' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], ], ], diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.xml b/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.xml index 50b9abb8f17ae..5872a8e94bccf 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.xml +++ b/app/code/Magento/Webapi/Test/Unit/Model/Config/_files/webapi.xml @@ -39,6 +39,7 @@ + diff --git a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php index 359bd9d1a2820..593037891d0c8 100644 --- a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php +++ b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php @@ -24,6 +24,9 @@ class Converter implements \Magento\Framework\Config\ConverterInterface const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /**#@-*/ + /** + * @var array + */ private $allowedRouteMethods = [ \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET, \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST, @@ -33,7 +36,8 @@ class Converter implements \Magento\Framework\Config\ConverterInterface ]; /** - * {@inheritdoc} + * @inheritDoc + * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -82,7 +86,10 @@ private function mergeSynchronousInvocationMethodsData( } /** + * Checks if xml node can be converted + * * @param \DOMElement $node + * * @return bool */ private function canConvertXmlNode(\DOMElement $node) @@ -121,7 +128,10 @@ private function initServiceMethodsKey(array &$result, $serviceClass, $serviceMe } /** + * Returns service class + * * @param \DOMElement $service + * * @return null|string */ private function getServiceClass(\DOMElement $service) @@ -132,7 +142,10 @@ private function getServiceClass(\DOMElement $service) } /** + * Returns service method + * * @param \DOMElement $service + * * @return null|string */ private function getServiceMethod(\DOMElement $service) @@ -143,7 +156,10 @@ private function getServiceMethod(\DOMElement $service) } /** + * Checks if synchronous method invocation only + * * @param \DOMElement $serviceNode + * * @return bool */ private function isSynchronousMethodInvocationOnly(\DOMElement $serviceNode) @@ -154,7 +170,10 @@ private function isSynchronousMethodInvocationOnly(\DOMElement $serviceNode) } /** + * Checks if synchronous invocation only true + * * @param \DOMElement $synchronousInvocationOnlyNode + * * @return bool|mixed */ private function isSynchronousInvocationOnlyTrue(\DOMElement $synchronousInvocationOnlyNode = null) @@ -172,7 +191,9 @@ private function isSynchronousInvocationOnlyTrue(\DOMElement $synchronousInvocat /** * Convert and merge "route" nodes, which represent route customizations + * * @param \DOMDocument $source + * * @return array */ private function convertRouteCustomizations($source) @@ -197,7 +218,10 @@ private function convertRouteCustomizations($source) } /** + * Returns route url + * * @param \DOMElement $route + * * @return null|string */ private function getRouteUrl($route) @@ -207,7 +231,10 @@ private function getRouteUrl($route) } /** + * Returns route alias + * * @param \DOMElement $route + * * @return null|string */ private function getRouteAlias($route) @@ -217,7 +244,10 @@ private function getRouteAlias($route) } /** + * Returns route method + * * @param \DOMElement $route + * * @return null|string */ private function getRouteMethod($route) @@ -228,7 +258,10 @@ private function getRouteMethod($route) } /** + * Validates method of route + * * @param string $method + * * @return bool */ private function validateRouteMethod($method) diff --git a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.php b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.php index 96cd8073ab563..60a982c58a6cc 100644 --- a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.php +++ b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.php @@ -24,8 +24,8 @@ ], ], 'routes' => [ - 'asyncProducts' => ['POST' => 'async/V1/products'], - 'asyncBulkCmsBlocks' => ['POST' => 'async/bulk/V1/cmsBlock'], - 'asyncCustomers' => ['POST' => 'async/V1/customers'] + 'asyncProducts' => ['POST' => 'async/V1/products', 'input-array-size-limit' => 30], + 'asyncBulkCmsBlocks' => ['POST' => 'async/bulk/V1/cmsBlock', 'input-array-size-limit' => null], + 'asyncCustomers' => ['POST' => 'async/V1/customers', 'input-array-size-limit' => null] ] ]; diff --git a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.xml b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.xml index be119c7da707d..4f7ae18066aeb 100644 --- a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.xml +++ b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Converter/webapi_async.xml @@ -17,7 +17,9 @@ - + + + diff --git a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async.php b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async.php index e41578b6fbdc8..2db3040cc9330 100644 --- a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async.php +++ b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async.php @@ -24,8 +24,8 @@ ], ], 'routes' => [ - 'asyncProducts' => ['POST' => 'async/bulk/V1/products'], - 'asyncBulkCmsPages' => ['POST' => 'async/bulk/V1/cmsPage'], - 'asyncCustomers' => ['POST' => 'async/V1/customers'] + 'asyncProducts' => ['POST' => 'async/bulk/V1/products','input-array-size-limit' => null], + 'asyncBulkCmsPages' => ['POST' => 'async/bulk/V1/cmsPage', 'input-array-size-limit' => 50], + 'asyncCustomers' => ['POST' => 'async/V1/customers', 'input-array-size-limit' => null] ] ]; diff --git a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async_1.xml b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async_1.xml index bd9895c7eef1e..4856c6f0a701e 100644 --- a/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async_1.xml +++ b/app/code/Magento/WebapiAsync/Test/Unit/Model/ServiceConfig/_files/Reader/webapi_async_1.xml @@ -16,6 +16,8 @@ - + + + diff --git a/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapi.php b/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapi.php index 57c8fbf45c63c..7d2be5f5a82d2 100644 --- a/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapi.php +++ b/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapi.php @@ -13,7 +13,8 @@ ], 'secure' => false, 'realMethod' => 'item', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], 'create' => [ 'resources' => [ @@ -21,7 +22,8 @@ ], 'secure' => false, 'realMethod' => 'create', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], ], ], @@ -34,7 +36,8 @@ ], 'secure' => false, 'realMethod' => 'getPreconfiguredItem', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], ], ], @@ -47,7 +50,8 @@ ], 'secure' => false, 'realMethod' => 'item', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], 'itemDefault' => [ 'resources' => [ @@ -60,7 +64,8 @@ 'force' => true, 'value' => null, ], - ] + ], + 'input-array-size-limit' => null, ], 'create' => [ 'resources' => [ @@ -73,7 +78,8 @@ 'force' => true, 'value' => null, ], - ] + ], + 'input-array-size-limit' => null, ], ], ], @@ -87,7 +93,8 @@ ], 'secure' => false, 'realMethod' => 'item', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], 'create' => [ 'resources' => [ @@ -101,7 +108,8 @@ 'force' => true, 'value' => null, ], - ] + ], + 'input-array-size-limit' => 50, ], 'delete' => [ 'resources' => [ @@ -110,7 +118,8 @@ ], 'secure' => false, 'realMethod' => 'delete', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], 'update' => [ 'resources' => [ @@ -119,7 +128,8 @@ ], 'secure' => false, 'realMethod' => 'update', - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ], ], ], @@ -136,8 +146,8 @@ 'resources' => [ 'Magento_TestModuleMSC::resource1' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], ], '/V1/testmoduleMSC' => [ @@ -150,8 +160,8 @@ 'resources' => [ 'Magento_TestModuleMSC::resource3' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], ], '/V1/testmodule1/:id' => [ @@ -164,8 +174,8 @@ 'resources' => [ 'Magento_Test1::resource1' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], ], '/V1/testmodule1' => [ @@ -184,6 +194,7 @@ 'value' => null, ], ], + 'input-array-size-limit' => null, ], 'POST' => [ 'secure' => false, @@ -200,6 +211,7 @@ 'value' => null, ], ], + 'input-array-size-limit' => null, ], ], '/V2/testmodule1/:id' => [ @@ -213,8 +225,8 @@ 'Magento_Test1::resource1' => true, 'Magento_Test1::resource2' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], 'DELETE' => [ 'secure' => false, @@ -226,8 +238,8 @@ 'Magento_Test1::resource1' => true, 'Magento_Test1::resource2' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], 'PUT' => [ 'secure' => false, @@ -239,8 +251,8 @@ 'Magento_Test1::resource1' => true, 'Magento_Test1::resource2' => true, ], - 'parameters' => [ - ], + 'parameters' => [], + 'input-array-size-limit' => null, ], ], '/V2/testmodule1' => [ @@ -260,6 +272,7 @@ 'value' => null, ], ], + 'input-array-size-limit' => 50, ], ], '/V2/testmoduleMSC/itemPreconfigured' => [ @@ -274,6 +287,7 @@ 'Magento_TestModuleMSC::resource2' => true, ], 'parameters' => [], + 'input-array-size-limit' => null, ] ] ], diff --git a/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapiA.xml b/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapiA.xml index 389908309b2a2..c84aedf09cf1a 100644 --- a/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapiA.xml +++ b/dev/tests/integration/testsuite/Magento/Webapi/Model/Config/_files/webapiA.xml @@ -35,7 +35,7 @@ - + null diff --git a/dev/tests/integration/testsuite/Magento/Webapi/Model/Soap/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Webapi/Model/Soap/ConfigTest.php index ceca6403b2c96..e0457df1fac19 100644 --- a/dev/tests/integration/testsuite/Magento/Webapi/Model/Soap/ConfigTest.php +++ b/dev/tests/integration/testsuite/Magento/Webapi/Model/Soap/ConfigTest.php @@ -90,7 +90,8 @@ public function testGetServiceMethodInfo() 'resources' => [ 'Magento_Customer::customer', ], - 'parameters' => [] + 'parameters' => [], + 'input-array-size-limit' => null, ]; $actual = $this->soapConfig->getServiceMethodInfo( 'customerCustomerRepositoryV1GetById', diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php index 6aef1a61922c8..c1f52651659dd 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php @@ -39,6 +39,7 @@ use Magento\Webapi\Test\Unit\Service\Entity\SimpleData; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -83,6 +84,11 @@ class ServiceInputProcessorTest extends TestCase */ private $defaultPageSizeSetter; + /** + * @var InputArraySizeLimitValue + */ + private $inputArraySizeLimitValue; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -167,6 +173,8 @@ function () use ($objectManager) { ->disableOriginalConstructor() ->getMock(); + $this->inputArraySizeLimitValue = $this->createMock(InputArraySizeLimitValue::class); + $this->defaultPageSizeSetter = self::getMockBuilder(DefaultPageSizeSetter::class) ->disableOriginalConstructor() ->getMock(); @@ -180,7 +188,11 @@ function () use ($objectManager) { 'attributeValueFactory' => $this->attributeValueFactoryMock, 'methodsMap' => $this->methodsMap, 'serviceTypeToEntityTypeMap' => $this->serviceTypeToEntityTypeMap, - 'serviceInputValidator' => new EntityArrayValidator(50, $this->inputLimitConfig), + 'serviceInputValidator' => new EntityArrayValidator( + 50, + $this->inputLimitConfig, + $this->inputArraySizeLimitValue + ), 'defaultPageSizeSetter' => $this->defaultPageSizeSetter, 'defaultPageSize' => 123 ] diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidator/InputArraySizeLimitValueTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidator/InputArraySizeLimitValueTest.php new file mode 100644 index 0000000000000..8b3d089225b06 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidator/InputArraySizeLimitValueTest.php @@ -0,0 +1,95 @@ +requestMock = $this->createMock(Request::class); + $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); + $this->inputArraySizeLimitValue = new InputArraySizeLimitValue( + $this->requestMock, + $this->deploymentConfigMock + ); + } + + /** + * @throws FileSystemException + * @throws RuntimeException + */ + public function testIfValueNotNull() + { + $this->requestMock->expects(self::never()) + ->method('getPathInfo'); + $this->deploymentConfigMock->expects(self::never()) + ->method('get'); + $this->inputArraySizeLimitValue->set(3); + $this->assertEquals(3, $this->inputArraySizeLimitValue->get()); + } + + /** + * @throws FileSystemException + * @throws RuntimeException + */ + public function testIfValueNullAndRequestIsAsync() + { + $this->requestMock->expects(self::once()) + ->method('getPathInfo') + ->willReturn('/async/V1/path'); + $this->deploymentConfigMock->expects(self::once()) + ->method('get') + ->willReturn(40); + $this->assertEquals(40, $this->inputArraySizeLimitValue->get()); + } + + /** + * @throws FileSystemException + * @throws RuntimeException + */ + public function testIfValueNullAndRequestIsSync() + { + $this->requestMock->expects(self::once()) + ->method('getPathInfo') + ->willReturn('/V1/path'); + $this->deploymentConfigMock->expects(self::once()) + ->method('get') + ->willReturn(50); + $this->assertEquals(50, $this->inputArraySizeLimitValue->get()); + } +} diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php index 6185e80cd342f..e81eb14e3d6eb 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php @@ -11,6 +11,7 @@ use Magento\Framework\Exception\InvalidArgumentException; use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider; use Magento\Framework\Webapi\Validator\EntityArrayValidator; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -22,19 +23,64 @@ class EntityArrayValidatorTest extends TestCase /** * @var IOLimitConfigProvider|MockObject */ - private $config; + private $configMock; + + /** + * @var InputArraySizeLimitValue|MockObject + */ + private $inputArraySizeLimitValueMock; /** * @var EntityArrayValidator */ - private $validator; + private EntityArrayValidator $validator; + /** + * @inheritDoc + */ protected function setUp(): void { - $this->config = self::getMockBuilder(IOLimitConfigProvider::class) - ->disableOriginalConstructor() - ->getMock(); - $this->validator = new EntityArrayValidator(3, $this->config); + $this->configMock = $this->createMock(IOLimitConfigProvider::class); + $this->inputArraySizeLimitValueMock = $this->createMock(InputArraySizeLimitValue::class); + $this->validator = new EntityArrayValidator( + 3, + $this->configMock, + $this->inputArraySizeLimitValueMock + ); + } + + /** + * @doesNotPerformAssertions + */ + public function testAllowsDataWhenBelowLimitWhenUsingRouteInputLimit() + { + $this->configMock->expects(self::once()) + ->method('isInputLimitingEnabled') + ->willReturn(true); + $this->inputArraySizeLimitValueMock->expects(self::once()) + ->method('get') + ->willReturn(5); + $this->configMock->expects(self::never()) + ->method('getComplexArrayItemLimit'); + $this->validator->validateComplexArrayType("foo", array_fill(0,5,[])); + } + + /** + * @doesNotPerformAssertions + */ + public function testFailsDataWhenAboveLimitUsingRouteInputLimit() + { + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage('Maximum items of type "foo" is 4'); + $this->configMock->expects(self::once()) + ->method('isInputLimitingEnabled') + ->willReturn(true); + $this->inputArraySizeLimitValueMock->expects(self::once()) + ->method('get') + ->willReturn(4); + $this->configMock->expects(self::never()) + ->method('getComplexArrayItemLimit'); + $this->validator->validateComplexArrayType("foo", array_fill(0,5,[])); } /** @@ -42,9 +88,16 @@ protected function setUp(): void */ public function testAllowsDataWhenBelowLimit() { - $this->config->method('isInputLimitingEnabled') + $this->configMock->expects(self::once()) + ->method('isInputLimitingEnabled') ->willReturn(true); - $this->validator->validateComplexArrayType("foo", [[],[],[]]); + $this->inputArraySizeLimitValueMock->expects(self::once()) + ->method('get') + ->willReturn(null); + $this->configMock->expects(self::once()) + ->method('getComplexArrayItemLimit') + ->willReturn(null); + $this->validator->validateComplexArrayType("foo", array_fill(0,3,[])); } /** @@ -52,31 +105,54 @@ public function testAllowsDataWhenBelowLimit() */ public function testAllowsDataWhenBelowLimitUsingConfig() { - $this->config->method('isInputLimitingEnabled') + $this->configMock->expects(self::once()) + ->method('isInputLimitingEnabled') ->willReturn(true); - $this->config->method('getComplexArrayItemLimit') + $this->inputArraySizeLimitValueMock->expects(self::once()) + ->method('get') + ->willReturn(null); + $this->configMock->expects(self::once()) + ->method('getComplexArrayItemLimit') ->willReturn(6); - $this->validator->validateComplexArrayType("foo", [[],[],[],[],[]]); + $this->validator->validateComplexArrayType("foo", array_fill(0,5,[])); } + /** + * @doesNotPerformAssertions + */ public function testFailsDataWhenAboveLimit() { $this->expectException(InvalidArgumentException::class); $this->expectErrorMessage('Maximum items of type "foo" is 3'); - $this->config->method('isInputLimitingEnabled') + $this->configMock->expects(self::once()) + ->method('isInputLimitingEnabled') ->willReturn(true); - $this->validator->validateComplexArrayType("foo", [[],[],[],[]]); + $this->inputArraySizeLimitValueMock->expects(self::once()) + ->method('get') + ->willReturn(null); + $this->configMock->expects(self::once()) + ->method('getComplexArrayItemLimit') + ->willReturn(null); + $this->validator->validateComplexArrayType("foo", array_fill(0,4,[])); } + /** + * @doesNotPerformAssertions + */ public function testFailsDataWhenAboveLimitUsingConfig() { $this->expectException(InvalidArgumentException::class); $this->expectErrorMessage('Maximum items of type "foo" is 6'); - $this->config->method('isInputLimitingEnabled') + $this->configMock->expects(self::once()) + ->method('isInputLimitingEnabled') ->willReturn(true); - $this->config->method('getComplexArrayItemLimit') + $this->inputArraySizeLimitValueMock->expects(self::once()) + ->method('get') + ->willReturn(null); + $this->configMock->expects(self::once()) + ->method('getComplexArrayItemLimit') ->willReturn(6); - $this->validator->validateComplexArrayType("foo", [[],[],[],[],[],[],[]]); + $this->validator->validateComplexArrayType("foo", array_fill(0,7,[])); } /** @@ -84,8 +160,11 @@ public function testFailsDataWhenAboveLimitUsingConfig() */ public function testAboveLimitWithDisabledLimiting() { - $this->config->method('isInputLimitingEnabled') + $this->configMock->expects(self::once()) + ->method('isInputLimitingEnabled') ->willReturn(false); - $this->validator->validateComplexArrayType("foo", [[],[],[],[],[],[],[]]); + $this->configMock->expects(self::never()) + ->method('getComplexArrayItemLimit'); + $this->validator->validateComplexArrayType("foo", array_fill(0,7,[])); } } diff --git a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php index 514dd4601cd40..65a3df9bd7696 100644 --- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php +++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php @@ -52,7 +52,7 @@ public function __construct( } /** - * {@inheritDoc} + * @inheritDoc * * @throws FileSystemException|RuntimeException */ @@ -76,7 +76,7 @@ public function validateComplexArrayType(string $className, array $items): void } /** - * {@inheritDoc} + * @inheritDoc * phpcs:disable Magento2.CodeAnalysis.EmptyBlock */ public function validateEntityValue(object $entity, string $propertyName, $value): void From 8cb2b80500f8a26304d8e5a8c3b487cb1b3831d9 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 10 Nov 2021 07:08:16 -0600 Subject: [PATCH 07/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Webapi/Controller/Soap/Request/Handler.php | 17 ++++++++++++++++- .../Rest/Asynchronous/InputParamsResolver.php | 1 - .../Unit/Validator/EntityArrayValidatorTest.php | 14 +++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index c8367934a36e3..7ea3d8d693bf4 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -156,10 +156,25 @@ public function __call($operation, $arguments) } $service = $this->_objectManager->get($serviceClass); $inputData = $this->prepareOperationInput($serviceClass, $serviceMethodInfo, $arguments); - $outputData = call_user_func_array([$service, $serviceMethod], $inputData); + $outputData = $this->runServiceMethod($service, $serviceMethod, $inputData); return $this->_prepareResponseData($outputData, $serviceClass, $serviceMethod); } + /** + * Runs service method + * + * @param $service + * @param $serviceMethod + * @param $inputData + * + * @return false|mixed + * + */ + private function runServiceMethod($service, $serviceMethod, $inputData) + { + return call_user_func_array([$service, $serviceMethod], $inputData); + } + /** * Convert arguments received from SOAP server to arguments to pass to a service. * diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index f4d41aa2e5230..f2a364e02f245 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -91,7 +91,6 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); - } /** diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php index e81eb14e3d6eb..7d368e7acad5a 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/Validator/EntityArrayValidatorTest.php @@ -62,7 +62,7 @@ public function testAllowsDataWhenBelowLimitWhenUsingRouteInputLimit() ->willReturn(5); $this->configMock->expects(self::never()) ->method('getComplexArrayItemLimit'); - $this->validator->validateComplexArrayType("foo", array_fill(0,5,[])); + $this->validator->validateComplexArrayType("foo", array_fill(0,5, [])); } /** @@ -80,7 +80,7 @@ public function testFailsDataWhenAboveLimitUsingRouteInputLimit() ->willReturn(4); $this->configMock->expects(self::never()) ->method('getComplexArrayItemLimit'); - $this->validator->validateComplexArrayType("foo", array_fill(0,5,[])); + $this->validator->validateComplexArrayType("foo", array_fill(0,5, [])); } /** @@ -97,7 +97,7 @@ public function testAllowsDataWhenBelowLimit() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(null); - $this->validator->validateComplexArrayType("foo", array_fill(0,3,[])); + $this->validator->validateComplexArrayType("foo", array_fill(0,3, [])); } /** @@ -114,7 +114,7 @@ public function testAllowsDataWhenBelowLimitUsingConfig() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(6); - $this->validator->validateComplexArrayType("foo", array_fill(0,5,[])); + $this->validator->validateComplexArrayType("foo", array_fill(0,5, [])); } /** @@ -133,7 +133,7 @@ public function testFailsDataWhenAboveLimit() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(null); - $this->validator->validateComplexArrayType("foo", array_fill(0,4,[])); + $this->validator->validateComplexArrayType("foo", array_fill(0,4, [])); } /** @@ -152,7 +152,7 @@ public function testFailsDataWhenAboveLimitUsingConfig() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(6); - $this->validator->validateComplexArrayType("foo", array_fill(0,7,[])); + $this->validator->validateComplexArrayType("foo", array_fill(0,7, [])); } /** @@ -165,6 +165,6 @@ public function testAboveLimitWithDisabledLimiting() ->willReturn(false); $this->configMock->expects(self::never()) ->method('getComplexArrayItemLimit'); - $this->validator->validateComplexArrayType("foo", array_fill(0,7,[])); + $this->validator->validateComplexArrayType("foo", array_fill(0,7, [])); } } From de8cae8cb3d6f55974af3adc560144a0bc362bf2 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Thu, 11 Nov 2021 14:30:13 -0600 Subject: [PATCH 08/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Controller/Rest/InputParamsResolver.php | 24 +++++--------- .../Controller/Soap/Request/Handler.php | 17 ++-------- .../Rest/Asynchronous/InputParamsResolver.php | 17 ++-------- .../WebapiAsync/Plugin/Rest/Config.php | 1 + .../Webapi/ServiceInputProcessor.php | 33 ++++++++++++++----- .../Test/Unit/ServiceInputProcessorTest.php | 3 +- 6 files changed, 41 insertions(+), 54 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php index b50c51ffbe4fe..10100336a32b3 100644 --- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php +++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php @@ -10,11 +10,11 @@ use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Reflection\MethodsMap; use Magento\Framework\Webapi\Exception; use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Rest\Request as RestRequest; -use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\Router\Route; /** @@ -57,11 +57,6 @@ class InputParamsResolver */ private $methodsMap; - /** - * @var InputArraySizeLimitValue - */ - private $inputArraySizeLimitValue; - /** * Initialize dependencies * @@ -71,7 +66,6 @@ class InputParamsResolver * @param Router $router * @param RequestValidator $requestValidator * @param MethodsMap|null $methodsMap - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( RestRequest $request, @@ -80,7 +74,6 @@ public function __construct( Router $router, RequestValidator $requestValidator, MethodsMap $methodsMap = null, - ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; @@ -89,26 +82,25 @@ public function __construct( $this->requestValidator = $requestValidator; $this->methodsMap = $methodsMap ?: ObjectManager::getInstance() ->get(MethodsMap::class); - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?: ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); } /** * Process and resolve input parameters * * @return array - * @throws Exception + * @throws Exception|AuthorizationException */ public function resolve() { $this->requestValidator->validate(); $route = $this->getRoute(); - $serviceMethodName = $route->getServiceMethod(); - $serviceClassName = $route->getServiceClass(); - $inputData = $this->getInputData(); - $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - return $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); + return $this->serviceInputProcessor->process( + $route->getServiceClass(), + $route->getServiceMethod(), + $this->getInputData(), + $route->getInputArraySizeLimit() + ); } /** diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index 7ea3d8d693bf4..79d25a5773c43 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -18,7 +18,6 @@ use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Request as SoapRequest; use Magento\Framework\Webapi\Exception as WebapiException; -use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Model\Soap\Config as SoapConfig; use Magento\Framework\Reflection\MethodsMap; @@ -80,11 +79,6 @@ class Handler */ private $paramsOverrider; - /** - * @var InputArraySizeLimitValue - */ - private $inputArraySizeLimitValue; - /** * Initialize dependencies. * @@ -97,7 +91,6 @@ class Handler * @param DataObjectProcessor $dataObjectProcessor * @param MethodsMap $methodsMapProcessor * @param ParamsOverrider|null $paramsOverrider - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( SoapRequest $request, @@ -108,8 +101,7 @@ public function __construct( ServiceInputProcessor $serviceInputProcessor, DataObjectProcessor $dataObjectProcessor, MethodsMap $methodsMapProcessor, - ?ParamsOverrider $paramsOverrider = null, - ?InputArraySizeLimitValue $inputArraySizeLimitValue = null + ?ParamsOverrider $paramsOverrider = null ) { $this->_request = $request; $this->_objectManager = $objectManager; @@ -120,8 +112,6 @@ public function __construct( $this->_dataObjectProcessor = $dataObjectProcessor; $this->methodsMapProcessor = $methodsMapProcessor; $this->paramsOverrider = $paramsOverrider ?? ObjectManager::getInstance()->get(ParamsOverrider::class); - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); } /** @@ -183,7 +173,6 @@ private function runServiceMethod($service, $serviceMethod, $inputData) * @param array $arguments * @return array * @throws WebapiException - * @throws \Magento\Framework\Exception\InputException */ private function prepareOperationInput(string $serviceClass, array $methodMetadata, array $arguments): array { @@ -191,12 +180,12 @@ private function prepareOperationInput(string $serviceClass, array $methodMetada $arguments = reset($arguments); $arguments = $this->_dataObjectConverter->convertStdObjectToArray($arguments, true); $arguments = $this->paramsOverrider->override($arguments, $methodMetadata[ServiceMetadata::KEY_ROUTE_PARAMS]); - $this->inputArraySizeLimitValue->set($methodMetadata[ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT]); return $this->serviceInputProcessor->process( $serviceClass, $methodMetadata[ServiceMetadata::KEY_METHOD], - $arguments + $arguments, + $methodMetadata[ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT] ); } diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index f2a364e02f245..f49b03aef7aaf 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,13 +8,11 @@ namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Exception\InputException; use Magento\Framework\Webapi\Exception; use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; -use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Controller\Rest\RequestValidator; @@ -55,11 +53,6 @@ class InputParamsResolver */ private $isBulk; - /** - * @var InputArraySizeLimitValue|null - */ - private $inputArraySizeLimitValue; - /** * Initialize dependencies. * @@ -70,7 +63,6 @@ class InputParamsResolver * @param RequestValidator $requestValidator * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( RestRequest $request, @@ -79,8 +71,7 @@ public function __construct( Router $router, RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, - $isBulk = false, - ?InputArraySizeLimitValue $inputArraySizeLimitValue = null + $isBulk = false ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; @@ -89,8 +80,6 @@ public function __construct( $this->requestValidator = $requestValidator; $this->inputParamsResolver = $inputParamsResolver; $this->isBulk = $isBulk; - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); } /** @@ -112,7 +101,6 @@ public function resolve() $this->requestValidator->validate(); $webapiResolvedParams = []; $route = $this->getRoute(); - $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); foreach ($this->getInputData() as $key => $singleEntityParams) { $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams, $route); @@ -169,7 +157,8 @@ private function resolveBulkItemParams(array $inputData, Route $route): array return $this->serviceInputProcessor->process( $route->getServiceClass(), $route->getServiceMethod(), - $inputData + $inputData, + $route->getInputArraySizeLimit() ); } } diff --git a/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php b/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php index d163bc310649a..2d1626ad81ba1 100644 --- a/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php +++ b/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php @@ -44,6 +44,7 @@ public function __construct(ServiceConfig $serviceConfig) * @param Request $request * @return Route[] * @throws InputException + * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function afterGetRestRoutes(RestConfig $restConfig, array $routes, Request $request): array { diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index e766dc774d7cc..3631940e7c517 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -15,6 +15,7 @@ use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\SerializationException; use Magento\Framework\ObjectManager\ConfigInterface; use Magento\Framework\ObjectManagerInterface; @@ -24,6 +25,7 @@ use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; use Laminas\Code\Reflection\ClassReflection; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Framework\Webapi\Validator\IOLimit\DefaultPageSizeSetter; use Magento\Framework\Webapi\Validator\ServiceInputValidatorInterface; @@ -99,10 +101,15 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface private $defaultPageSize; /** - * @var DefaultPageSizeSetter|null + * @var DefaultPageSizeSetter */ private $defaultPageSizeSetter; + /** + * @var InputArraySizeLimitValue + */ + private $inputArraySizeLimitValue; + /** * Initialize dependencies. * @@ -117,6 +124,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param ServiceInputValidatorInterface|null $serviceInputValidator * @param int $defaultPageSize * @param DefaultPageSizeSetter|null $defaultPageSizeSetter + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -130,7 +138,8 @@ public function __construct( array $customAttributePreprocessors = [], ServiceInputValidatorInterface $serviceInputValidator = null, int $defaultPageSize = 20, - ?DefaultPageSizeSetter $defaultPageSizeSetter = null + ?DefaultPageSizeSetter $defaultPageSizeSetter = null, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->typeProcessor = $typeProcessor; $this->objectManager = $objectManager; @@ -147,6 +156,8 @@ public function __construct( $this->defaultPageSize = $defaultPageSize >= 10 ? $defaultPageSize : 10; $this->defaultPageSizeSetter = $defaultPageSizeSetter ?? ObjectManager::getInstance() ->get(DefaultPageSizeSetter::class); + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); } /** @@ -177,20 +188,21 @@ private function getNameFinder() * @param string $serviceClassName name of the service class that we are trying to call * @param string $serviceMethodName name of the method that we are trying to call * @param array $inputArray data to send to method in key-value format + * @param int|null $inputArraySizeLimit size limit for input array * @return array list of parameters that can be used to call the service method - * @throws WebapiException + * @throws Exception + * @throws LocalizedException */ - public function process($serviceClassName, $serviceMethodName, array $inputArray) + public function process($serviceClassName, $serviceMethodName, array $inputArray, ?int $inputArraySizeLimit = null) { $inputData = []; $inputError = []; + foreach ($this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName) as $param) { $paramName = $param[MethodsMap::METHOD_META_NAME]; $snakeCaseParamName = strtolower(preg_replace("/(?<=\\w)(?=[A-Z])/", "_$1", $paramName)); if (isset($inputArray[$paramName]) || isset($inputArray[$snakeCaseParamName])) { - $paramValue = isset($inputArray[$paramName]) - ? $inputArray[$paramName] - : $inputArray[$snakeCaseParamName]; + $paramValue = $inputArray[$paramName] ?? $inputArray[$snakeCaseParamName]; try { $inputData[] = $this->convertValue($paramValue, $param[MethodsMap::METHOD_META_TYPE]); @@ -205,7 +217,10 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray } } } + $this->processInputError($inputError); + $this->inputArraySizeLimitValue->set($inputArraySizeLimit); + return $inputData; } @@ -216,7 +231,7 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray * @param array $data * @return array * @throws \ReflectionException - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ private function getConstructorData(string $className, array $data): array { @@ -490,7 +505,7 @@ protected function _createDataObjectForTypeAndArrayValue($type, $customAttribute * @param mixed $data * @param string $type Convert given value to the this type * @return mixed - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function convertValue($data, $type) { diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php index c1f52651659dd..0a07ad43771c9 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php @@ -194,7 +194,8 @@ function () use ($objectManager) { $this->inputArraySizeLimitValue ), 'defaultPageSizeSetter' => $this->defaultPageSizeSetter, - 'defaultPageSize' => 123 + 'defaultPageSize' => 123, + 'inputArraySizeLimitValue' => $this->inputArraySizeLimitValue ] ); From 12b02ed9b0a303ce2b26f8d568533e31340f2618 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 11 Nov 2021 15:19:26 -0600 Subject: [PATCH 09/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../Model/CompositeTokenReader.php | 48 +++++++++ .../Integration/Model/OpaqueToken/Reader.php | 101 +++++++++++++++--- .../Model/UserToken/ExpirationValidator.php | 5 +- .../Unit/Model/CompositeTokenReaderTest.php | 69 ++++++++++++ .../UserToken/ExpirationValidatorTest.php | 15 ++- app/code/Magento/Integration/etc/di.xml | 9 +- app/code/Magento/JwtUserToken/etc/di.xml | 9 +- .../Authorization/AuthorizationConfig.php | 49 +++++++++ .../Authorization/AuthorizationConfigTest.php | 51 +++++++++ .../Magento/Webapi/etc/adminhtml/system.xml | 7 ++ .../Integration/Model/IntegrationTest.php | 43 ++++++++ 11 files changed, 388 insertions(+), 18 deletions(-) create mode 100644 app/code/Magento/Integration/Model/CompositeTokenReader.php create mode 100644 app/code/Magento/Integration/Test/Unit/Model/CompositeTokenReaderTest.php create mode 100644 app/code/Magento/Webapi/Model/Authorization/AuthorizationConfig.php create mode 100644 app/code/Magento/Webapi/Test/Unit/Model/Authorization/AuthorizationConfigTest.php diff --git a/app/code/Magento/Integration/Model/CompositeTokenReader.php b/app/code/Magento/Integration/Model/CompositeTokenReader.php new file mode 100644 index 0000000000000..0c53cf76b779b --- /dev/null +++ b/app/code/Magento/Integration/Model/CompositeTokenReader.php @@ -0,0 +1,48 @@ +readers = $readers; + } + + /** + * @inheritDoc + */ + public function read(string $token): UserToken + { + foreach ($this->readers as $reader) { + try { + return $reader->read($token); + } catch (UserTokenException $exception) { + continue; + } + } + + throw new UserTokenException('Composite reader could not read a token'); + } +} diff --git a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php index fedf613188840..f2d8d35a56426 100644 --- a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php +++ b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php @@ -8,14 +8,20 @@ namespace Magento\Integration\Model\OpaqueToken; +use Magento\Framework\App\ObjectManager; use Magento\Integration\Api\Data\UserToken; use Magento\Integration\Api\Exception\UserTokenException; +use Magento\Integration\Api\IntegrationServiceInterface; use Magento\Integration\Api\UserTokenReaderInterface; use Magento\Integration\Model\CustomUserContext; use Magento\Integration\Model\Oauth\Token; use Magento\Integration\Model\Oauth\TokenFactory; use Magento\Integration\Helper\Oauth\Data as OauthHelper; +use Magento\Webapi\Model\Authorization\AuthorizationConfig; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Reader implements UserTokenReaderInterface { /** @@ -28,14 +34,34 @@ class Reader implements UserTokenReaderInterface */ private $helper; + /** + * @var AuthorizationConfig + */ + private $authorizationConfig; + + /** + * @var IntegrationServiceInterface + */ + private IntegrationServiceInterface $integrationService; + /** * @param TokenFactory $tokenFactory * @param OauthHelper $helper + * @param AuthorizationConfig|null $authorizationConfig + * @param IntegrationServiceInterface|null $integrationService */ - public function __construct(TokenFactory $tokenFactory, OauthHelper $helper) - { + public function __construct( + TokenFactory $tokenFactory, + OauthHelper $helper, + ?AuthorizationConfig $authorizationConfig = null, + ?IntegrationServiceInterface $integrationService = null + ) { $this->tokenFactory = $tokenFactory; $this->helper = $helper; + $this->authorizationConfig = $authorizationConfig ?? ObjectManager::getInstance() + ->get(AuthorizationConfig::class); + $this->integrationService = $integrationService ?? ObjectManager::getInstance() + ->get(IntegrationServiceInterface::class); } /** @@ -43,7 +69,32 @@ public function __construct(TokenFactory $tokenFactory, OauthHelper $helper) */ public function read(string $token): UserToken { - /** @var Token $tokenModel */ + + $tokenModel = $this->getTokenModel($token); + $userType = (int) $tokenModel->getUserType(); + $this->validateUserType($userType); + $userId = $this->getUserId($tokenModel); + + $issued = \DateTimeImmutable::createFromFormat( + 'Y-m-d H:i:s', + $tokenModel->getCreatedAt(), + new \DateTimeZone('UTC') + ); + $lifetimeHours = $userType === CustomUserContext::USER_TYPE_ADMIN + ? $this->helper->getAdminTokenLifetime() : $this->helper->getCustomerTokenLifetime(); + $expires = $issued->add(new \DateInterval("PT{$lifetimeHours}H")); + + return new UserToken(new CustomUserContext((int) $userId, (int) $userType), new Data($issued, $expires)); + } + + /** + * Create the token model from the input + * + * @param string $token + * @return Token + */ + private function getTokenModel(string $token): Token + { $tokenModel = $this->tokenFactory->create(); $tokenModel = $tokenModel->load($token, 'token'); @@ -53,27 +104,49 @@ public function read(string $token): UserToken if ($tokenModel->getRevoked()) { throw new UserTokenException('Token was revoked'); } - $userType = (int) $tokenModel->getUserType(); - if ($userType !== CustomUserContext::USER_TYPE_ADMIN && $userType !== CustomUserContext::USER_TYPE_CUSTOMER) { + + return $tokenModel; + } + + /** + * Validate the given user type + * + * @param int $userType + */ + private function validateUserType(int $userType): void + { + if ($userType === CustomUserContext::USER_TYPE_INTEGRATION) { + if (!$this->authorizationConfig->isIntegrationAsBearerEnabled()) { + throw new UserTokenException('Invalid token found'); + } + } elseif ($userType !== CustomUserContext::USER_TYPE_ADMIN + && $userType !== CustomUserContext::USER_TYPE_CUSTOMER + ) { throw new UserTokenException('Invalid token found'); } + } + + /** + * Determine the user id for a given token + * + * @param Token $tokenModel + * @return int + */ + private function getUserId(Token $tokenModel): int + { + $userType = (int)$tokenModel->getUserType(); + if ($userType === CustomUserContext::USER_TYPE_ADMIN) { $userId = $tokenModel->getAdminId(); + } elseif ($userType === CustomUserContext::USER_TYPE_INTEGRATION) { + $userId = $this->integrationService->findByConsumerId($tokenModel->getConsumerId())->getId(); } else { $userId = $tokenModel->getCustomerId(); } if (!$userId) { throw new UserTokenException('Invalid token found'); } - $issued = \DateTimeImmutable::createFromFormat( - 'Y-m-d H:i:s', - $tokenModel->getCreatedAt(), - new \DateTimeZone('UTC') - ); - $lifetimeHours = $userType === CustomUserContext::USER_TYPE_ADMIN - ? $this->helper->getAdminTokenLifetime() : $this->helper->getCustomerTokenLifetime(); - $expires = $issued->add(new \DateInterval("PT{$lifetimeHours}H")); - return new UserToken(new CustomUserContext((int) $userId, (int) $userType), new Data($issued, $expires)); + return $userId; } } diff --git a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php index 3e19fed38aead..69b7ec6d68f93 100644 --- a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php +++ b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php @@ -8,6 +8,7 @@ namespace Magento\Integration\Model\UserToken; +use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\Exception\AuthorizationException; use Magento\Integration\Api\Data\UserToken; use Magento\Integration\Api\UserTokenValidatorInterface; @@ -33,7 +34,9 @@ public function __construct(DtUtil $datetimeUtil) */ public function validate(UserToken $token): void { - if ($token->getData()->getExpires()->getTimestamp() <= $this->datetimeUtil->gmtTimestamp()) { + if ($token->getUserContext()->getUserType() !== UserContextInterface::USER_TYPE_INTEGRATION + && $token->getData()->getExpires()->getTimestamp() <= $this->datetimeUtil->gmtTimestamp() + ) { throw new AuthorizationException(__('Consumer key has expired')); } } diff --git a/app/code/Magento/Integration/Test/Unit/Model/CompositeTokenReaderTest.php b/app/code/Magento/Integration/Test/Unit/Model/CompositeTokenReaderTest.php new file mode 100644 index 0000000000000..44f80e6a59dd9 --- /dev/null +++ b/app/code/Magento/Integration/Test/Unit/Model/CompositeTokenReaderTest.php @@ -0,0 +1,69 @@ +createMock(UserToken::class); + $reader1 = $this->createMock(UserTokenReaderInterface::class); + $reader1->method('read') + ->with('abc') + ->willReturn($token1); + + $token2 = $this->createMock(UserToken::class); + $reader2 = $this->createMock(UserTokenReaderInterface::class); + $reader2->method('read') + ->with('abc') + ->willReturn($token2); + + $composite = new CompositeTokenReader([$reader1, $reader2]); + + self::assertSame($token1, $composite->read('abc')); + } + + public function testCompositeReaderReturnsNextTokenOnError() + { + $reader1 = $this->createMock(UserTokenReaderInterface::class); + $reader1->method('read') + ->with('abc') + ->willThrowException(new UserTokenException('Fail')); + + $token2 = $this->createMock(UserToken::class); + $reader2 = $this->createMock(UserTokenReaderInterface::class); + $reader2->method('read') + ->with('abc') + ->willReturn($token2); + + $composite = new CompositeTokenReader([$reader1, $reader1, $reader2]); + + self::assertSame($token2, $composite->read('abc')); + } + + public function testCompositeReaderFailsWhenNoTokensFound() + { + $this->expectExceptionMessage('Composite reader could not read a token'); + $this->expectException(UserTokenException::class); + + $reader1 = $this->createMock(UserTokenReaderInterface::class); + $reader1->method('read') + ->with('abc') + ->willThrowException(new UserTokenException('Fail')); + + $composite = new CompositeTokenReader([$reader1, $reader1, $reader1]); + $composite->read('abc'); + } +} diff --git a/app/code/Magento/Integration/Test/Unit/Model/UserToken/ExpirationValidatorTest.php b/app/code/Magento/Integration/Test/Unit/Model/UserToken/ExpirationValidatorTest.php index 6bc1dad3087b0..9751f9bfa3d8e 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/UserToken/ExpirationValidatorTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/UserToken/ExpirationValidatorTest.php @@ -8,6 +8,7 @@ namespace Magento\Integration\Test\Unit\Model\UserToken; +use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\Exception\AuthorizationException; use Magento\Integration\Api\Data\UserToken; use Magento\Integration\Api\Data\UserTokenDataInterface; @@ -62,10 +63,22 @@ public function getUserTokens(): array ->willReturn(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2021-04-07 16:00:00')); $futureToken->method('getData')->willReturn($futureData); + $integrationToken = $this->createMock(UserToken::class); + $userContext = $this->createMock(UserContextInterface::class); + $userContext->method('getUserType') + ->willReturn(UserContextInterface::USER_TYPE_INTEGRATION); + $integrationData = $this->createMock(UserTokenDataInterface::class); + $integrationData->method('getExpires') + ->willReturn(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2021-04-07 12:00:00')); + $integrationToken->method('getData')->willReturn($pastData); + $integrationToken->method('getUserContext') + ->willReturn($userContext); + return [ 'past' => [$pastToken, false, $currentTs], 'exact' => [$exactToken, false, $currentTs], - 'future' => [$futureToken, true, $currentTs] + 'future' => [$futureToken, true, $currentTs], + 'integration' => [$integrationToken, true, $currentTs], ]; } diff --git a/app/code/Magento/Integration/etc/di.xml b/app/code/Magento/Integration/etc/di.xml index 9fe53af49f073..57430e302cac7 100644 --- a/app/code/Magento/Integration/etc/di.xml +++ b/app/code/Magento/Integration/etc/di.xml @@ -45,6 +45,13 @@ - + + + + + Magento\Integration\Model\OpaqueToken\Reader + + + diff --git a/app/code/Magento/JwtUserToken/etc/di.xml b/app/code/Magento/JwtUserToken/etc/di.xml index faa523aef1027..c70e34a8a31a3 100644 --- a/app/code/Magento/JwtUserToken/etc/di.xml +++ b/app/code/Magento/JwtUserToken/etc/di.xml @@ -7,7 +7,7 @@ --> - + @@ -24,4 +24,11 @@ + + + + Magento\JwtUserToken\Model\Reader + + + diff --git a/app/code/Magento/Webapi/Model/Authorization/AuthorizationConfig.php b/app/code/Magento/Webapi/Model/Authorization/AuthorizationConfig.php new file mode 100644 index 0000000000000..c7078dc98bedb --- /dev/null +++ b/app/code/Magento/Webapi/Model/Authorization/AuthorizationConfig.php @@ -0,0 +1,49 @@ +scopeConfig = $scopeConfig; + } + + /** + * Return if integration access tokens can be used as bearer tokens + * + * @return bool + */ + public function isIntegrationAsBearerEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + self::CONFIG_PATH_INTEGRATION_BEARER, + ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Authorization/AuthorizationConfigTest.php b/app/code/Magento/Webapi/Test/Unit/Model/Authorization/AuthorizationConfigTest.php new file mode 100644 index 0000000000000..7c18084c8dd6b --- /dev/null +++ b/app/code/Magento/Webapi/Test/Unit/Model/Authorization/AuthorizationConfigTest.php @@ -0,0 +1,51 @@ +scopeConfig = $this->createMock(ScopeConfigInterface::class); + $this->config = new AuthorizationConfig($this->scopeConfig); + } + + public function testEnabled() + { + $this->scopeConfig->method('isSetFlag') + ->with('webapi/authorization/enable_integration_as_bearer') + ->willReturn(true); + + self::assertTrue($this->config->isIntegrationAsBearerEnabled()); + } + + public function testDisabled() + { + $this->scopeConfig->method('isSetFlag') + ->with('webapi/authorization/enable_integration_as_bearer') + ->willReturn(false); + + self::assertFalse($this->config->isIntegrationAsBearerEnabled()); + } +} diff --git a/app/code/Magento/Webapi/etc/adminhtml/system.xml b/app/code/Magento/Webapi/etc/adminhtml/system.xml index d6b85994b8603..4fbb0b049830e 100644 --- a/app/code/Magento/Webapi/etc/adminhtml/system.xml +++ b/app/code/Magento/Webapi/etc/adminhtml/system.xml @@ -20,6 +20,13 @@ If empty, UTF-8 will be used. + + + + Magento\Config\Model\Config\Source\Yesno + + + diff --git a/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php b/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php index 489e7d2517527..cad830101a119 100644 --- a/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Integration\Model; +use Magento\Framework\App\ObjectManager; +use Magento\Integration\Api\OauthServiceInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Authentication\OauthHelper; @@ -64,4 +66,45 @@ public function testGetServiceCall() $this->assertEquals($itemId, $item['entity_id'], 'id field returned incorrectly'); $this->assertEquals($name, $item['name'], 'name field returned incorrectly'); } + + /** + * Test Integration access token cannot be used as Bearer token by default + */ + public function testIntegrationAsBearerTokenDefault() + { + $this->_markTestAsRestOnly(); + $oauthService = ObjectManager::getInstance()->get(OauthServiceInterface::class); + $accessToken = $oauthService->getAccessToken($this->integration->getConsumerId()); + $serviceInfo = [ + 'rest' => [ + 'token' => $accessToken, + 'resourcePath' => '/V1/store/storeViews', + 'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET, + ], + ]; + self::expectException(\Exception::class); + self::expectExceptionMessage('The consumer isn\'t authorized to access %resources.'); + $this->_webApiCall($serviceInfo); + } + + /** + * Test Integration access token can be used as Bearer token when explicitly enabled + * + * @magentoConfigFixture default_store webapi/authorization/enable_integration_as_bearer 1 + * @doesNotPerformAssertions + */ + public function testIntegrationAsBearerTokenEnabled() + { + $this->_markTestAsRestOnly(); + $oauthService = ObjectManager::getInstance()->get(OauthServiceInterface::class); + $accessToken = $oauthService->getAccessToken($this->integration->getConsumerId()); + $serviceInfo = [ + 'rest' => [ + 'token' => $accessToken->getToken(), + 'resourcePath' => '/V1/store/storeViews', + 'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET, + ], + ]; + $this->_webApiCall($serviceInfo); + } } From 1851fdde34557cbcde9c12d4ab03f024e38a6171 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 11 Nov 2021 15:25:25 -0600 Subject: [PATCH 10/37] AC-1619: Integration access tokens do not work as Bearer tokens - Stabilizing tests --- .../Model/Config}/AuthorizationConfig.php | 4 ++-- app/code/Magento/Integration/Model/OpaqueToken/Reader.php | 4 ++-- .../Test/Unit/Model/Config}/AuthorizationConfigTest.php | 8 ++++---- app/code/Magento/Integration/etc/adminhtml/system.xml | 4 ++++ app/code/Magento/Webapi/etc/adminhtml/system.xml | 7 ------- .../Magento/Integration/Model/IntegrationTest.php | 2 +- 6 files changed, 13 insertions(+), 16 deletions(-) rename app/code/Magento/{Webapi/Model/Authorization => Integration/Model/Config}/AuthorizationConfig.php (87%) rename app/code/Magento/{Webapi/Test/Unit/Model/Authorization => Integration/Test/Unit/Model/Config}/AuthorizationConfigTest.php (80%) diff --git a/app/code/Magento/Webapi/Model/Authorization/AuthorizationConfig.php b/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php similarity index 87% rename from app/code/Magento/Webapi/Model/Authorization/AuthorizationConfig.php rename to app/code/Magento/Integration/Model/Config/AuthorizationConfig.php index c7078dc98bedb..4103f27b0ce3d 100644 --- a/app/code/Magento/Webapi/Model/Authorization/AuthorizationConfig.php +++ b/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\Webapi\Model\Authorization; +namespace Magento\Integration\Model\Config; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Store\Model\ScopeInterface; @@ -19,7 +19,7 @@ class AuthorizationConfig /** * XML Path for Enable Integration as Bearer */ - const CONFIG_PATH_INTEGRATION_BEARER = 'webapi/authorization/enable_integration_as_bearer'; + const CONFIG_PATH_INTEGRATION_BEARER = 'oauth/consumer/enable_integration_as_bearer'; /** * @var ScopeConfigInterface diff --git a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php index f2d8d35a56426..390abd8a2e0a9 100644 --- a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php +++ b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php @@ -13,11 +13,11 @@ use Magento\Integration\Api\Exception\UserTokenException; use Magento\Integration\Api\IntegrationServiceInterface; use Magento\Integration\Api\UserTokenReaderInterface; +use Magento\Integration\Model\Config\AuthorizationConfig; use Magento\Integration\Model\CustomUserContext; use Magento\Integration\Model\Oauth\Token; use Magento\Integration\Model\Oauth\TokenFactory; use Magento\Integration\Helper\Oauth\Data as OauthHelper; -use Magento\Webapi\Model\Authorization\AuthorizationConfig; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -147,6 +147,6 @@ private function getUserId(Token $tokenModel): int throw new UserTokenException('Invalid token found'); } - return $userId; + return (int)$userId; } } diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Authorization/AuthorizationConfigTest.php b/app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php similarity index 80% rename from app/code/Magento/Webapi/Test/Unit/Model/Authorization/AuthorizationConfigTest.php rename to app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php index 7c18084c8dd6b..c16ff8191cb12 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Authorization/AuthorizationConfigTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php @@ -6,10 +6,10 @@ declare(strict_types=1); -namespace Magento\Webapi\Test\Unit\Model\Authorization; +namespace Magento\Integration\Test\Unit\Model\Config\Authorization; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Webapi\Model\Authorization\AuthorizationConfig; +use Magento\Integration\Model\Config\AuthorizationConfig; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -34,7 +34,7 @@ protected function setUp(): void public function testEnabled() { $this->scopeConfig->method('isSetFlag') - ->with('webapi/authorization/enable_integration_as_bearer') + ->with('oauth/consumer/enable_integration_as_bearer') ->willReturn(true); self::assertTrue($this->config->isIntegrationAsBearerEnabled()); @@ -43,7 +43,7 @@ public function testEnabled() public function testDisabled() { $this->scopeConfig->method('isSetFlag') - ->with('webapi/authorization/enable_integration_as_bearer') + ->with('oauth/consumer/enable_integration_as_bearer') ->willReturn(false); self::assertFalse($this->config->isIntegrationAsBearerEnabled()); diff --git a/app/code/Magento/Integration/etc/adminhtml/system.xml b/app/code/Magento/Integration/etc/adminhtml/system.xml index 3d465a9642805..2db4c9a7e5412 100644 --- a/app/code/Magento/Integration/etc/adminhtml/system.xml +++ b/app/code/Magento/Integration/etc/adminhtml/system.xml @@ -54,6 +54,10 @@ Timeout for OAuth consumer credentials Post request within X seconds. required-entry validate-zero-or-greater validate-number + + Magento\Config\Model\Config\Source\Yesno + + diff --git a/app/code/Magento/Webapi/etc/adminhtml/system.xml b/app/code/Magento/Webapi/etc/adminhtml/system.xml index 4fbb0b049830e..d6b85994b8603 100644 --- a/app/code/Magento/Webapi/etc/adminhtml/system.xml +++ b/app/code/Magento/Webapi/etc/adminhtml/system.xml @@ -20,13 +20,6 @@ If empty, UTF-8 will be used. - - - - Magento\Config\Model\Config\Source\Yesno - - - diff --git a/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php b/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php index cad830101a119..46efb08b97c15 100644 --- a/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php @@ -90,7 +90,7 @@ public function testIntegrationAsBearerTokenDefault() /** * Test Integration access token can be used as Bearer token when explicitly enabled * - * @magentoConfigFixture default_store webapi/authorization/enable_integration_as_bearer 1 + * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 1 * @doesNotPerformAssertions */ public function testIntegrationAsBearerTokenEnabled() From 940daf977bd0c90433b66717575f6dae896da36a Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 11 Nov 2021 21:11:44 -0600 Subject: [PATCH 11/37] AC-1619: Integration access tokens do not work as Bearer tokens - Static fixes --- .../Test/Unit/Model/Config/AuthorizationConfigTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php b/app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php index c16ff8191cb12..96832b3c5d1a5 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\Integration\Test\Unit\Model\Config\Authorization; +namespace Magento\Integration\Test\Unit\Model\Config; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Integration\Model\Config\AuthorizationConfig; From e3cc57b14d2de101543bd04ba7cbb59c82f7f8fc Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Thu, 11 Nov 2021 22:01:42 -0600 Subject: [PATCH 12/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Webapi/Controller/Rest/InputParamsResolver.php | 9 +++++++-- app/code/Magento/WebapiAsync/Plugin/Rest/Config.php | 2 +- .../Test/Integrity/Magento/Webapi/Model/ConfigTest.php | 2 -- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php index 10100336a32b3..87fdb053a6665 100644 --- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php +++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php @@ -11,6 +11,8 @@ use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\AuthorizationException; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Reflection\MethodsMap; use Magento\Framework\Webapi\Exception; use Magento\Framework\Webapi\ServiceInputProcessor; @@ -19,6 +21,7 @@ /** * This class is responsible for retrieving resolved input data + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class InputParamsResolver { @@ -73,7 +76,7 @@ public function __construct( ServiceInputProcessor $serviceInputProcessor, Router $router, RequestValidator $requestValidator, - MethodsMap $methodsMap = null, + MethodsMap $methodsMap = null ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; @@ -88,7 +91,7 @@ public function __construct( * Process and resolve input parameters * * @return array - * @throws Exception|AuthorizationException + * @throws Exception|AuthorizationException|LocalizedException */ public function resolve() { @@ -107,6 +110,7 @@ public function resolve() * Get API input data * * @return array + * @throws InputException|Exception */ public function getInputData() { @@ -136,6 +140,7 @@ public function getInputData() * Retrieve current route. * * @return Route + * @throws Exception */ public function getRoute() { diff --git a/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php b/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php index 2d1626ad81ba1..739f27cbd090c 100644 --- a/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php +++ b/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php @@ -44,7 +44,7 @@ public function __construct(ServiceConfig $serviceConfig) * @param Request $request * @return Route[] * @throws InputException - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterGetRestRoutes(RestConfig $restConfig, array $routes, Request $request): array { diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Webapi/Model/ConfigTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Webapi/Model/ConfigTest.php index c6d90fd55af93..359b5bda7cf6c 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Webapi/Model/ConfigTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Webapi/Model/ConfigTest.php @@ -22,7 +22,6 @@ public function testSchemaUsingInvalidXml($expectedErrors = null) "Element 'route', attribute 'method': [facet 'enumeration'] The value 'PATCH' is not an element of the set {'GET', 'PUT', 'POST', 'DELETE'}.", "Element 'route', attribute 'method': 'PATCH' is not a valid value of the local atomic type.", "Element 'service': The attribute 'method' is required but missing.", - "Element 'data': Missing child element(s). Expected is ( parameter ).", "Element 'route': Missing child element(s). Expected is ( service ).", "Element 'route': Missing child element(s). Expected is ( resources ).", ]; @@ -40,7 +39,6 @@ public function testFileSchemaUsingInvalidXml($expectedErrors = null) "Element 'route', attribute 'method': [facet 'enumeration'] The value 'PATCH' is not an element of the set {'GET', 'PUT', 'POST', 'DELETE'}.", "Element 'route', attribute 'method': 'PATCH' is not a valid value of the local atomic type.", "Element 'service': The attribute 'method' is required but missing.", - "Element 'data': Missing child element(s). Expected is ( parameter ).", ]; // @codingStandardsIgnoreEnd parent::testFileSchemaUsingInvalidXml($expectedErrors); From b1cb867cb15620a09bcb0e9fb1c6cbb078aee917 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Fri, 12 Nov 2021 06:27:45 -0600 Subject: [PATCH 13/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Webapi/Controller/Soap/Request/Handler.php | 6 +++--- .../TestFramework/TestCase/WebapiAbstract.php | 4 +++- .../Config/ConfigOptionsListConstants.php | 2 -- .../Framework/Webapi/ServiceInputProcessor.php | 2 -- .../Unit/Validator/EntityArrayValidatorTest.php | 14 +++++++------- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index 79d25a5773c43..d2f6d2c96b241 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -153,9 +153,9 @@ public function __call($operation, $arguments) /** * Runs service method * - * @param $service - * @param $serviceMethod - * @param $inputData + * @param object $service + * @param string $serviceMethod + * @param array $inputData * * @return false|mixed * diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 7ccab097d7778..8a5268721feaf 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -567,7 +567,9 @@ public function processRestExceptionResult(\Exception $e) { $error = json_decode($e->getMessage(), true); //Remove line breaks and replace with space - $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message'])); + $error['message'] = isset($error['message']) + ? trim(preg_replace('/\s+/', ' ', $error['message'])) + : ''; // remove trace and type, will only be present if server is in dev mode unset($error['trace']); unset($error['type']); diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index 8b9a2bdba93c7..7d21b53b45a8b 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -92,8 +92,6 @@ class ConfigOptionsListConstants const CONFIG_PATH_BACKEND_OPTIONS = 'backend_options'; /** - * @deprecated - * * Definition format constant. */ const INPUT_KEY_DEFINITION_FORMAT = 'definition-format'; diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 3631940e7c517..bfd3bf14e937b 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -1,7 +1,5 @@ willReturn(5); $this->configMock->expects(self::never()) ->method('getComplexArrayItemLimit'); - $this->validator->validateComplexArrayType("foo", array_fill(0,5, [])); + $this->validator->validateComplexArrayType("foo", array_fill(0, 5, [])); } /** @@ -80,7 +80,7 @@ public function testFailsDataWhenAboveLimitUsingRouteInputLimit() ->willReturn(4); $this->configMock->expects(self::never()) ->method('getComplexArrayItemLimit'); - $this->validator->validateComplexArrayType("foo", array_fill(0,5, [])); + $this->validator->validateComplexArrayType("foo", array_fill(0, 5, [])); } /** @@ -97,7 +97,7 @@ public function testAllowsDataWhenBelowLimit() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(null); - $this->validator->validateComplexArrayType("foo", array_fill(0,3, [])); + $this->validator->validateComplexArrayType("foo", array_fill(0, 3, [])); } /** @@ -114,7 +114,7 @@ public function testAllowsDataWhenBelowLimitUsingConfig() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(6); - $this->validator->validateComplexArrayType("foo", array_fill(0,5, [])); + $this->validator->validateComplexArrayType("foo", array_fill(0, 5, [])); } /** @@ -133,7 +133,7 @@ public function testFailsDataWhenAboveLimit() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(null); - $this->validator->validateComplexArrayType("foo", array_fill(0,4, [])); + $this->validator->validateComplexArrayType("foo", array_fill(0, 4, [])); } /** @@ -152,7 +152,7 @@ public function testFailsDataWhenAboveLimitUsingConfig() $this->configMock->expects(self::once()) ->method('getComplexArrayItemLimit') ->willReturn(6); - $this->validator->validateComplexArrayType("foo", array_fill(0,7, [])); + $this->validator->validateComplexArrayType("foo", array_fill(0, 7, [])); } /** @@ -165,6 +165,6 @@ public function testAboveLimitWithDisabledLimiting() ->willReturn(false); $this->configMock->expects(self::never()) ->method('getComplexArrayItemLimit'); - $this->validator->validateComplexArrayType("foo", array_fill(0,7, [])); + $this->validator->validateComplexArrayType("foo", array_fill(0, 7, [])); } } From 617f9b5e951392baa53ac7614b93eea39411cbd7 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Fri, 12 Nov 2021 10:04:12 -0600 Subject: [PATCH 14/37] AC-465: Allow to configure input limit for RESTful endpoints --- app/code/Magento/Webapi/Controller/Soap/Request/Handler.php | 5 ++--- app/code/Magento/Webapi/Model/ServiceMetadata.php | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index d2f6d2c96b241..29df049d82d64 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -156,13 +156,12 @@ public function __call($operation, $arguments) * @param object $service * @param string $serviceMethod * @param array $inputData - * * @return false|mixed - * */ private function runServiceMethod($service, $serviceMethod, $inputData) { - return call_user_func_array([$service, $serviceMethod], $inputData); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return call_user_func_array([$service, $serviceMethod], $inputData); } /** diff --git a/app/code/Magento/Webapi/Model/ServiceMetadata.php b/app/code/Magento/Webapi/Model/ServiceMetadata.php index d6c56f0a46920..99a421508d2ca 100644 --- a/app/code/Magento/Webapi/Model/ServiceMetadata.php +++ b/app/code/Magento/Webapi/Model/ServiceMetadata.php @@ -142,6 +142,7 @@ protected function initServicesMetadata() $methods ); foreach ($services[$serviceName][self::KEY_SERVICE_METHODS] as $methodName => &$methodMetadata) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $methodMetadata = array_merge( $methodMetadata, $reflectedMethodsMetadata[$methodMetadata[self::KEY_METHOD]] From efc00806f02aaca949116e8458cff57f621dff77 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Mon, 15 Nov 2021 13:30:52 -0600 Subject: [PATCH 15/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Magento/TestFramework/TestCase/WebapiAbstract.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 8a5268721feaf..7ccab097d7778 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -567,9 +567,7 @@ public function processRestExceptionResult(\Exception $e) { $error = json_decode($e->getMessage(), true); //Remove line breaks and replace with space - $error['message'] = isset($error['message']) - ? trim(preg_replace('/\s+/', ' ', $error['message'])) - : ''; + $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message'])); // remove trace and type, will only be present if server is in dev mode unset($error['trace']); unset($error['type']); From 1d0e246756a160899751e7ef6e98d032d526d78f Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Tue, 16 Nov 2021 17:19:49 -0600 Subject: [PATCH 16/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Magento/TestFramework/TestCase/WebapiAbstract.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 7ccab097d7778..cf43250cad7ab 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -566,6 +566,10 @@ protected function _restoreAppConfig() public function processRestExceptionResult(\Exception $e) { $error = json_decode($e->getMessage(), true); + if(empty($error) && json_last_error() !== JSON_ERROR_NONE){ + $error['message'] = $e->getMessage(); + } + //Remove line breaks and replace with space $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message'])); // remove trace and type, will only be present if server is in dev mode From 877417dfc35dcae579b275bf41d628bb7eea6c85 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Tue, 16 Nov 2021 19:17:12 -0600 Subject: [PATCH 17/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Magento/TestFramework/TestCase/WebapiAbstract.php | 9 +++++++-- .../testsuite/Magento/Webapi/Routing/CoreRoutingTest.php | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index cf43250cad7ab..187d47af6b2ef 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -568,13 +568,18 @@ public function processRestExceptionResult(\Exception $e) $error = json_decode($e->getMessage(), true); if(empty($error) && json_last_error() !== JSON_ERROR_NONE){ $error['message'] = $e->getMessage(); + $error['code'] = $e->getCode(); + $error['file'] = $e->getFile(); + $error['trace'] = $e->getTrace(); + $error['traceAsString'] = $e->getTraceAsString(); + $error['previous'] = $e->getPrevious(); } //Remove line breaks and replace with space $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message'])); // remove trace and type, will only be present if server is in dev mode - unset($error['trace']); - unset($error['type']); +// unset($error['trace']); +// unset($error['type']); return $error; } diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php index 00eaa70deaa04..c5480886dc925 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php @@ -116,7 +116,12 @@ public function testRequestParamsUnexpectedValueException(): void $exceptionResult = $this->processRestExceptionResult($e); $actualMessage = $exceptionResult['message']; $this->assertStringNotContainsString($unexpectedMessage, $actualMessage); - $this->assertStringContainsString($expectedMessage, $actualMessage); + $this->assertStringContainsString('getMessage', $e->getMessage()); + $this->assertStringContainsString('getCode', $e->getCode()); + $this->assertStringContainsString('getTraceAsString', $e->getTraceAsString()); + $this->assertStringContainsString('getFile', $e->getFile()); + $this->assertStringContainsString('line', $e->getLine()); + $this->assertArrayHasKey('getTrace', $e->getTrace()); } } } From 0040aa593cf4c3304c72711b931bb20a28d9f89e Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 17 Nov 2021 06:19:13 -0600 Subject: [PATCH 18/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../TestFramework/TestCase/WebapiAbstract.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 187d47af6b2ef..f5f4c381f2186 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -565,21 +565,25 @@ protected function _restoreAppConfig() */ public function processRestExceptionResult(\Exception $e) { + echo 'pickachu'; + var_dump([ + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), +// 'trace' => $e->getTrace(), + 'traceAsString' => $e->getTraceAsString(), +// 'previous' => $e->getPrevious(), + ]); $error = json_decode($e->getMessage(), true); - if(empty($error) && json_last_error() !== JSON_ERROR_NONE){ + if (empty($error) && json_last_error() !== JSON_ERROR_NONE) { $error['message'] = $e->getMessage(); - $error['code'] = $e->getCode(); - $error['file'] = $e->getFile(); - $error['trace'] = $e->getTrace(); - $error['traceAsString'] = $e->getTraceAsString(); - $error['previous'] = $e->getPrevious(); } //Remove line breaks and replace with space $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message'])); // remove trace and type, will only be present if server is in dev mode -// unset($error['trace']); -// unset($error['type']); + unset($error['trace']); + unset($error['type']); return $error; } From ac488cd4e1abfa6fcfd028188a6356f8480553e5 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 17 Nov 2021 10:30:06 -0600 Subject: [PATCH 19/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../TestCase/HttpClient/CurlClient.php | 13 +++++++++++-- .../TestFramework/TestCase/Webapi/Adapter/Rest.php | 12 +++++++++++- .../TestCase/Webapi/Adapter/Rest/RestClient.php | 13 ++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php index f56577d3f92c2..4be226e00bdb5 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php @@ -118,8 +118,17 @@ public function put($url, $data, $headers = []) $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT; $headers[] = 'Content-Length: ' . strlen($data); $curlOpts[CURLOPT_POSTFIELDS] = $data; - - $resp = $this->invokeApi($url, $curlOpts, $headers); + try{ + $resp = $this->invokeApi($url, $curlOpts, $headers); + }catch (\Exception $e){ + var_dump([ + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'traceAsString' => $e->getTraceAsString(), + ]); + throw $e; + } return $resp["body"]; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php index 49a1fa9de9435..62e59f9b00d3d 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php @@ -99,7 +99,17 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat $response = $this->restClient->post($resourcePath, $arguments, $authHeader); break; case Request::HTTP_METHOD_PUT: - $response = $this->restClient->put($resourcePath, $arguments, $authHeader); + try{ + $response = $this->restClient->put($resourcePath, $arguments, $authHeader); + }catch (\Exception $e){ + var_dump([ + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'traceAsString' => $e->getTraceAsString(), + ]); + throw $e; + } break; case Request::HTTP_METHOD_DELETE: $response = $this->restClient->delete($resourcePath, $authHeader); diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php index 18f9e2e09bbed..7ba086e588c56 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php @@ -103,7 +103,18 @@ public function put($resourcePath, $data, $headers = []) $data = ''; } } - $responseBody = $this->curlClient->put($url, $data, $headers); + try{ + $responseBody = $this->curlClient->put($url, $data, $headers); + }catch (\Exception $e){ + var_dump([ + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'traceAsString' => $e->getTraceAsString(), + ]); + throw $e; + } + return $this->jsonSerializer->jsonDecode($responseBody); } From 3a07e8f310e234c7e9d6e42f066a162299de9b71 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Thu, 18 Nov 2021 18:42:27 -0600 Subject: [PATCH 20/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../TestFramework/TestCase/HttpClient/CurlClient.php | 8 ++++---- .../TestFramework/TestCase/Webapi/Adapter/Rest.php | 8 ++++---- .../TestCase/Webapi/Adapter/Rest/RestClient.php | 8 ++++---- .../Magento/TestFramework/TestCase/WebapiAbstract.php | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php index 4be226e00bdb5..92070e67cd05d 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php @@ -122,10 +122,10 @@ public function put($url, $data, $headers = []) $resp = $this->invokeApi($url, $curlOpts, $headers); }catch (\Exception $e){ var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), - 'traceAsString' => $e->getTraceAsString(), + 'message' => $e->getPrevious()->getMessage(), + 'code' => $e->getPrevious()->getCode(), + 'file' => $e->getPrevious()->getFile(), + 'traceAsString' => $e->getPrevious()->getTraceAsString(), ]); throw $e; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php index 62e59f9b00d3d..df0ec650a30cf 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php @@ -103,10 +103,10 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat $response = $this->restClient->put($resourcePath, $arguments, $authHeader); }catch (\Exception $e){ var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), - 'traceAsString' => $e->getTraceAsString(), + 'message' => $e->getPrevious()->getMessage(), + 'code' => $e->getPrevious()->getCode(), + 'file' => $e->getPrevious()->getFile(), + 'traceAsString' => $e->getPrevious()->getTraceAsString(), ]); throw $e; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php index 7ba086e588c56..d44a33c44d494 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php @@ -107,10 +107,10 @@ public function put($resourcePath, $data, $headers = []) $responseBody = $this->curlClient->put($url, $data, $headers); }catch (\Exception $e){ var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), - 'traceAsString' => $e->getTraceAsString(), + 'message' => $e->getPrevious()->getMessage(), + 'code' => $e->getPrevious()->getCode(), + 'file' => $e->getPrevious()->getFile(), + 'traceAsString' => $e->getPrevious()->getTraceAsString(), ]); throw $e; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index f5f4c381f2186..7af2766bd838d 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -567,11 +567,11 @@ public function processRestExceptionResult(\Exception $e) { echo 'pickachu'; var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), + 'message' => $e->getPrevious()->getMessage(), + 'code' => $e->getPrevious()->getCode(), + 'file' => $e->getPrevious()->getFile(), // 'trace' => $e->getTrace(), - 'traceAsString' => $e->getTraceAsString(), + 'traceAsString' => $e->getPrevious()->getTraceAsString(), // 'previous' => $e->getPrevious(), ]); $error = json_decode($e->getMessage(), true); From 005ed60b2aee08c2c520f26bb73cbd19215e0410 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Thu, 18 Nov 2021 23:33:03 -0600 Subject: [PATCH 21/37] AC-465: Allow to configure input limit for RESTful endpoints --- lib/internal/Magento/Framework/App/Bootstrap.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/internal/Magento/Framework/App/Bootstrap.php b/lib/internal/Magento/Framework/App/Bootstrap.php index 83f292cfdf703..934c0cb13eca9 100644 --- a/lib/internal/Magento/Framework/App/Bootstrap.php +++ b/lib/internal/Magento/Framework/App/Bootstrap.php @@ -429,6 +429,8 @@ public function isDeveloperMode() */ protected function terminate(\Throwable $e) { + echo "ramamba"; + var_dump($e); /** @var Response $response */ $response = $this->objectManager->get(Response::class); $response->clearHeaders(); From cc456b7412b74b323f5718843c9d48f8854dfc4d Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Fri, 19 Nov 2021 07:00:11 -0600 Subject: [PATCH 22/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../TestFramework/TestCase/HttpClient/CurlClient.php | 8 ++++---- .../TestFramework/TestCase/Webapi/Adapter/Rest.php | 8 ++++---- .../TestCase/Webapi/Adapter/Rest/RestClient.php | 8 ++++---- .../Magento/TestFramework/TestCase/WebapiAbstract.php | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php index 92070e67cd05d..4be226e00bdb5 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php @@ -122,10 +122,10 @@ public function put($url, $data, $headers = []) $resp = $this->invokeApi($url, $curlOpts, $headers); }catch (\Exception $e){ var_dump([ - 'message' => $e->getPrevious()->getMessage(), - 'code' => $e->getPrevious()->getCode(), - 'file' => $e->getPrevious()->getFile(), - 'traceAsString' => $e->getPrevious()->getTraceAsString(), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'traceAsString' => $e->getTraceAsString(), ]); throw $e; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php index df0ec650a30cf..62e59f9b00d3d 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php @@ -103,10 +103,10 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat $response = $this->restClient->put($resourcePath, $arguments, $authHeader); }catch (\Exception $e){ var_dump([ - 'message' => $e->getPrevious()->getMessage(), - 'code' => $e->getPrevious()->getCode(), - 'file' => $e->getPrevious()->getFile(), - 'traceAsString' => $e->getPrevious()->getTraceAsString(), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'traceAsString' => $e->getTraceAsString(), ]); throw $e; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php index d44a33c44d494..7ba086e588c56 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php @@ -107,10 +107,10 @@ public function put($resourcePath, $data, $headers = []) $responseBody = $this->curlClient->put($url, $data, $headers); }catch (\Exception $e){ var_dump([ - 'message' => $e->getPrevious()->getMessage(), - 'code' => $e->getPrevious()->getCode(), - 'file' => $e->getPrevious()->getFile(), - 'traceAsString' => $e->getPrevious()->getTraceAsString(), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'traceAsString' => $e->getTraceAsString(), ]); throw $e; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 7af2766bd838d..f5f4c381f2186 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -567,11 +567,11 @@ public function processRestExceptionResult(\Exception $e) { echo 'pickachu'; var_dump([ - 'message' => $e->getPrevious()->getMessage(), - 'code' => $e->getPrevious()->getCode(), - 'file' => $e->getPrevious()->getFile(), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), // 'trace' => $e->getTrace(), - 'traceAsString' => $e->getPrevious()->getTraceAsString(), + 'traceAsString' => $e->getTraceAsString(), // 'previous' => $e->getPrevious(), ]); $error = json_decode($e->getMessage(), true); From cd3dba8df587a0526a191c146c9565f031914c3b Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 22 Nov 2021 09:30:19 -0600 Subject: [PATCH 23/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../Integration/Model/OpaqueToken/Reader.php | 3 +++ .../Model/UserToken/ExpirationValidator.php | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php index 390abd8a2e0a9..3401653db0e7f 100644 --- a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php +++ b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php @@ -20,6 +20,8 @@ use Magento\Integration\Helper\Oauth\Data as OauthHelper; /** + * Reads user token data + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Reader implements UserTokenReaderInterface @@ -112,6 +114,7 @@ private function getTokenModel(string $token): Token * Validate the given user type * * @param int $userType + * @throws UserTokenException */ private function validateUserType(int $userType): void { diff --git a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php index 69b7ec6d68f93..d86125885541a 100644 --- a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php +++ b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php @@ -34,10 +34,30 @@ public function __construct(DtUtil $datetimeUtil) */ public function validate(UserToken $token): void { - if ($token->getUserContext()->getUserType() !== UserContextInterface::USER_TYPE_INTEGRATION - && $token->getData()->getExpires()->getTimestamp() <= $this->datetimeUtil->gmtTimestamp() - ) { + if (!$this->isIntegrationToken($token) && $this->isTokenExpired($token)) { throw new AuthorizationException(__('Consumer key has expired')); } } + + /** + * Check if a token is expired + * + * @param UserToken $token + * @return bool + */ + private function isTokenExpired(UserToken $token): bool + { + return $token->getData()->getExpires()->getTimestamp() <= $this->datetimeUtil->gmtTimestamp(); + } + + /** + * Check if a token is an integration token + * + * @param UserToken $token + * @return bool + */ + private function isIntegrationToken(UserToken $token): bool + { + return $token->getUserContext()->getUserType() === UserContextInterface::USER_TYPE_INTEGRATION; + } } From a638ddfa7afe1976b8000057854a1a1b5f5e76b6 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 22 Nov 2021 09:31:10 -0600 Subject: [PATCH 24/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../Integration/Model/UserToken/ExpirationValidator.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php index d86125885541a..b22f146d6c64e 100644 --- a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php +++ b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php @@ -14,6 +14,9 @@ use Magento\Integration\Api\UserTokenValidatorInterface; use Magento\Framework\Stdlib\DateTime\DateTime as DtUtil; +/** + * Validates if a token is expired + */ class ExpirationValidator implements UserTokenValidatorInterface { /** From d64f03e8e83c4fe9cdc803ebd469fc98fe1572d0 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 22 Nov 2021 18:04:26 -0600 Subject: [PATCH 25/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../Model/Authorization/SoapUserContext.php | 18 +++-- .../Magento/Webapi/etc/webapi_soap/di.xml | 15 ++-- .../WsdlGenerationFromDataObjectTest.php | 76 ++++++++++++++++--- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php index 78de066ac31ee..9754be6ebefe1 100644 --- a/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php +++ b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php @@ -8,6 +8,7 @@ use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\App\ObjectManager; +use Magento\Integration\Model\Config\AuthorizationConfig; use Magento\Integration\Model\Oauth\Token; use Magento\Integration\Model\Oauth\TokenFactory; use Magento\Integration\Api\IntegrationServiceInterface; @@ -51,24 +52,30 @@ class SoapUserContext implements UserContextInterface */ private $integrationService; + /** + * @var AuthorizationConfig + */ + private $authorizationConfig; + /** * Initialize dependencies. * * @param Request $request * @param TokenFactory $tokenFactory * @param IntegrationServiceInterface $integrationService - * @param DateTime|null $dateTime - * @param Date|null $date - * @param OauthHelper|null $oauthHelper + * @param AuthorizationConfig|null $authorizationConfig */ public function __construct( Request $request, TokenFactory $tokenFactory, - IntegrationServiceInterface $integrationService + IntegrationServiceInterface $integrationService, + ?AuthorizationConfig $authorizationConfig = null ) { $this->request = $request; $this->tokenFactory = $tokenFactory; $this->integrationService = $integrationService; + $this->authorizationConfig = $authorizationConfig ?? ObjectManager::getInstance() + ->get(AuthorizationConfig::class); } /** @@ -110,10 +117,11 @@ private function processRequest() //phpcs:ignore CopyPaste return; } $tokenType = strtolower($headerPieces[0]); - if ($tokenType !== 'bearer') { + if ($tokenType !== 'bearer' || !$this->authorizationConfig->isIntegrationAsBearerEnabled()) { $this->isRequestProcessed = true; return; } + $bearerToken = $headerPieces[1]; /** @var Token $token */ diff --git a/app/code/Magento/Webapi/etc/webapi_soap/di.xml b/app/code/Magento/Webapi/etc/webapi_soap/di.xml index 5e77b320a7a57..333774b9c2bc9 100644 --- a/app/code/Magento/Webapi/etc/webapi_soap/di.xml +++ b/app/code/Magento/Webapi/etc/webapi_soap/di.xml @@ -6,22 +6,21 @@ */ --> - - - Magento\Webapi\Controller\Soap\Request - - - - Magento\Webapi\Model\Authorization\SoapUserContext - 9 + + Magento\Webapi\Model\Authorization\OauthUserContext + 5 Magento\Webapi\Model\Authorization\TokenUserContext 10 + + Magento\Webapi\Model\Authorization\SoapUserContext + 15 + Magento\Webapi\Model\Authorization\GuestUserContext 100 diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php index 91be257429314..8d1c2608a8167 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php @@ -6,6 +6,8 @@ namespace Magento\Webapi; +use Magento\TestFramework\Authentication\OauthHelper; +use Magento\TestFramework\Authentication\Rest\OauthClient; use Magento\TestFramework\Helper\Bootstrap; /** @@ -33,6 +35,39 @@ protected function setUp(): void parent::setUp(); } + public function testDisabledIntegrationAsBearer() + { + $wsdlUrl = $this->_getBaseWsdlUrl() . 'testModule5AllSoapAndRestV1,testModule5AllSoapAndRestV2'; + $accessCredentials = \Magento\TestFramework\Authentication\OauthHelper::getApiAccessCredentials()['key']; + $connection = curl_init($wsdlUrl); + curl_setopt($connection, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($connection, CURLOPT_HTTPHEADER, ['header' => "Authorization: Bearer " . $accessCredentials]); + $responseContent = curl_exec($connection); + $this->assertEquals(curl_getinfo($connection, CURLINFO_HTTP_CODE), 401); + $this->assertStringContainsString( + "The consumer isn't authorized to access %resources.", + htmlspecialchars_decode($responseContent, ENT_QUOTES) + ); + } + + public function testAuthenticationWithOAuth() + { + $wsdlUrl = $this->_getBaseWsdlUrl() . 'testModule5AllSoapAndRestV2'; + $this->_soapUrl = "{$this->_baseUrl}/soap/{$this->_storeCode}?services=testModule5AllSoapAndRestV2"; + $this->isSingleService = true; + + $connection = curl_init($wsdlUrl); + curl_setopt($connection, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($connection, CURLOPT_HTTPHEADER, ['header' => $this->getAuthHeader($wsdlUrl)]); + $responseContent = curl_exec($connection); + $this->assertEquals(curl_getinfo($connection, CURLINFO_HTTP_CODE), 200); + $wsdlContent = $this->_convertXmlToString($responseContent); + $this->checkAll($wsdlContent); + } + + /** + * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 1 + */ public function testMultiServiceWsdl() { $this->_soapUrl = "{$this->_baseUrl}/soap/{$this->_storeCode}" @@ -41,14 +76,12 @@ public function testMultiServiceWsdl() $wsdlContent = $this->_convertXmlToString($this->_getWsdlContent($wsdlUrl)); $this->isSingleService = false; - $this->_checkTypesDeclaration($wsdlContent); - $this->_checkPortTypeDeclaration($wsdlContent); - $this->_checkBindingDeclaration($wsdlContent); - $this->_checkServiceDeclaration($wsdlContent); - $this->_checkMessagesDeclaration($wsdlContent); - $this->_checkFaultsDeclaration($wsdlContent); + $this->checkAll($wsdlContent); } + /** + * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 1 + */ public function testSingleServiceWsdl() { $this->_soapUrl = "{$this->_baseUrl}/soap/{$this->_storeCode}?services=testModule5AllSoapAndRestV2"; @@ -56,12 +89,7 @@ public function testSingleServiceWsdl() $wsdlContent = $this->_convertXmlToString($this->_getWsdlContent($wsdlUrl)); $this->isSingleService = true; - $this->_checkTypesDeclaration($wsdlContent); - $this->_checkPortTypeDeclaration($wsdlContent); - $this->_checkBindingDeclaration($wsdlContent); - $this->_checkServiceDeclaration($wsdlContent); - $this->_checkMessagesDeclaration($wsdlContent); - $this->_checkFaultsDeclaration($wsdlContent); + $this->checkAll($wsdlContent); } public function testNoAuthorizedServices() @@ -983,4 +1011,28 @@ protected function _checkFaultsComplexTypeSection($wsdlContent) 'Details wrapped errors (array of wrapped errors) complex types declaration is invalid.' ); } + + private function getAuthHeader(string $url): string + { + $accessCredentials = OauthHelper::getApiAccessCredentials(); + /** @var OauthClient $oAuthClient */ + $oAuthClient = $accessCredentials['oauth_client']; + return $oAuthClient->buildOauthAuthorizationHeader( + $url, + $accessCredentials['key'], + $accessCredentials['secret'], + [], + 'GET' + )[0]; + } + + private function checkAll(string $wsdlContent): void + { + $this->_checkTypesDeclaration($wsdlContent); + $this->_checkPortTypeDeclaration($wsdlContent); + $this->_checkBindingDeclaration($wsdlContent); + $this->_checkServiceDeclaration($wsdlContent); + $this->_checkMessagesDeclaration($wsdlContent); + $this->_checkFaultsDeclaration($wsdlContent); + } } From f55651cc07d97de7116aea7126231246aa7b76e9 Mon Sep 17 00:00:00 2001 From: rizwankhan Date: Tue, 23 Nov 2021 14:57:57 +0530 Subject: [PATCH 26/37] AC-461: Add ReCaptcha support to coupon code --- .../view/frontend/web/template/payment/discount.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html index 649c1ba4bfe59..f7cf894445a12 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html @@ -44,10 +44,10 @@ + + + - - - From 32b33fc96ad2d1a2bbcec847f998752787de488a Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 23 Nov 2021 11:55:09 -0600 Subject: [PATCH 27/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../_files/Magento/TestModule1/etc/config.xml | 16 ++++++++++++++++ .../Integration/Model/IntegrationTest.php | 2 +- .../Webapi/WsdlGenerationFromDataObjectTest.php | 9 +++------ 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 dev/tests/api-functional/_files/Magento/TestModule1/etc/config.xml diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/config.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/config.xml new file mode 100644 index 0000000000000..6b1bff7578908 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/config.xml @@ -0,0 +1,16 @@ + + + + + + + 1 + + + + diff --git a/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php b/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php index 46efb08b97c15..a5d78c9054936 100644 --- a/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php @@ -69,6 +69,7 @@ public function testGetServiceCall() /** * Test Integration access token cannot be used as Bearer token by default + * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 0 */ public function testIntegrationAsBearerTokenDefault() { @@ -90,7 +91,6 @@ public function testIntegrationAsBearerTokenDefault() /** * Test Integration access token can be used as Bearer token when explicitly enabled * - * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 1 * @doesNotPerformAssertions */ public function testIntegrationAsBearerTokenEnabled() diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php index 8d1c2608a8167..4a05a83351685 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php @@ -35,6 +35,9 @@ protected function setUp(): void parent::setUp(); } + /** + * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 0 + */ public function testDisabledIntegrationAsBearer() { $wsdlUrl = $this->_getBaseWsdlUrl() . 'testModule5AllSoapAndRestV1,testModule5AllSoapAndRestV2'; @@ -65,9 +68,6 @@ public function testAuthenticationWithOAuth() $this->checkAll($wsdlContent); } - /** - * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 1 - */ public function testMultiServiceWsdl() { $this->_soapUrl = "{$this->_baseUrl}/soap/{$this->_storeCode}" @@ -79,9 +79,6 @@ public function testMultiServiceWsdl() $this->checkAll($wsdlContent); } - /** - * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 1 - */ public function testSingleServiceWsdl() { $this->_soapUrl = "{$this->_baseUrl}/soap/{$this->_storeCode}?services=testModule5AllSoapAndRestV2"; From d2c897bfe667323deca68916981caaf4830362bd Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 1 Dec 2021 08:08:31 -0600 Subject: [PATCH 28/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../TestCase/HttpClient/CurlClient.php | 13 ++----------- .../TestFramework/TestCase/Webapi/Adapter/Rest.php | 12 +----------- .../TestCase/Webapi/Adapter/Rest/RestClient.php | 13 +------------ .../TestFramework/TestCase/WebapiAbstract.php | 13 ------------- .../Magento/Webapi/Routing/CoreRoutingTest.php | 7 +------ lib/internal/Magento/Framework/App/Bootstrap.php | 2 -- .../Framework/Webapi/ServiceInputProcessor.php | 2 +- 7 files changed, 6 insertions(+), 56 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php index 4be226e00bdb5..f56577d3f92c2 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php @@ -118,17 +118,8 @@ public function put($url, $data, $headers = []) $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT; $headers[] = 'Content-Length: ' . strlen($data); $curlOpts[CURLOPT_POSTFIELDS] = $data; - try{ - $resp = $this->invokeApi($url, $curlOpts, $headers); - }catch (\Exception $e){ - var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), - 'traceAsString' => $e->getTraceAsString(), - ]); - throw $e; - } + + $resp = $this->invokeApi($url, $curlOpts, $headers); return $resp["body"]; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php index 62e59f9b00d3d..49a1fa9de9435 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php @@ -99,17 +99,7 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat $response = $this->restClient->post($resourcePath, $arguments, $authHeader); break; case Request::HTTP_METHOD_PUT: - try{ - $response = $this->restClient->put($resourcePath, $arguments, $authHeader); - }catch (\Exception $e){ - var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), - 'traceAsString' => $e->getTraceAsString(), - ]); - throw $e; - } + $response = $this->restClient->put($resourcePath, $arguments, $authHeader); break; case Request::HTTP_METHOD_DELETE: $response = $this->restClient->delete($resourcePath, $authHeader); diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php index 7ba086e588c56..18f9e2e09bbed 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/RestClient.php @@ -103,18 +103,7 @@ public function put($resourcePath, $data, $headers = []) $data = ''; } } - try{ - $responseBody = $this->curlClient->put($url, $data, $headers); - }catch (\Exception $e){ - var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), - 'traceAsString' => $e->getTraceAsString(), - ]); - throw $e; - } - + $responseBody = $this->curlClient->put($url, $data, $headers); return $this->jsonSerializer->jsonDecode($responseBody); } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index f5f4c381f2186..7ccab097d7778 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -565,20 +565,7 @@ protected function _restoreAppConfig() */ public function processRestExceptionResult(\Exception $e) { - echo 'pickachu'; - var_dump([ - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile(), -// 'trace' => $e->getTrace(), - 'traceAsString' => $e->getTraceAsString(), -// 'previous' => $e->getPrevious(), - ]); $error = json_decode($e->getMessage(), true); - if (empty($error) && json_last_error() !== JSON_ERROR_NONE) { - $error['message'] = $e->getMessage(); - } - //Remove line breaks and replace with space $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message'])); // remove trace and type, will only be present if server is in dev mode diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php index c5480886dc925..00eaa70deaa04 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php @@ -116,12 +116,7 @@ public function testRequestParamsUnexpectedValueException(): void $exceptionResult = $this->processRestExceptionResult($e); $actualMessage = $exceptionResult['message']; $this->assertStringNotContainsString($unexpectedMessage, $actualMessage); - $this->assertStringContainsString('getMessage', $e->getMessage()); - $this->assertStringContainsString('getCode', $e->getCode()); - $this->assertStringContainsString('getTraceAsString', $e->getTraceAsString()); - $this->assertStringContainsString('getFile', $e->getFile()); - $this->assertStringContainsString('line', $e->getLine()); - $this->assertArrayHasKey('getTrace', $e->getTrace()); + $this->assertStringContainsString($expectedMessage, $actualMessage); } } } diff --git a/lib/internal/Magento/Framework/App/Bootstrap.php b/lib/internal/Magento/Framework/App/Bootstrap.php index 934c0cb13eca9..83f292cfdf703 100644 --- a/lib/internal/Magento/Framework/App/Bootstrap.php +++ b/lib/internal/Magento/Framework/App/Bootstrap.php @@ -429,8 +429,6 @@ public function isDeveloperMode() */ protected function terminate(\Throwable $e) { - echo "ramamba"; - var_dump($e); /** @var Response $response */ $response = $this->objectManager->get(Response::class); $response->clearHeaders(); diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index bfd3bf14e937b..8abefd0449ad0 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -195,6 +195,7 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray { $inputData = []; $inputError = []; + $this->inputArraySizeLimitValue->set($inputArraySizeLimit); foreach ($this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName) as $param) { $paramName = $param[MethodsMap::METHOD_META_NAME]; @@ -217,7 +218,6 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray } $this->processInputError($inputError); - $this->inputArraySizeLimitValue->set($inputArraySizeLimit); return $inputData; } From 4d9988e5ceded6d4ea0c278f8467d771f4268275 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 1 Dec 2021 11:10:21 -0600 Subject: [PATCH 29/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Controller/Soap/Request/Handler.php | 2 +- .../Magento/Webapi/Model/Config/Converter.php | 34 ++--- app/code/Magento/Webapi/Model/Rest/Config.php | 24 +-- .../Magento/Webapi/Model/ServiceMetadata.php | 28 ++-- .../Model/ServiceConfig/Converter.php | 12 +- .../Config/ConfigOptionsListConstants.php | 140 +++++++++--------- .../Webapi/ServiceInputProcessor.php | 2 +- .../ServiceInputProcessor/TestService.php | 4 +- 8 files changed, 123 insertions(+), 123 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index 29df049d82d64..9b7619b005bd0 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -32,7 +32,7 @@ */ class Handler { - const RESULT_NODE_NAME = 'result'; + public const RESULT_NODE_NAME = 'result'; /** * @var \Magento\Framework\Webapi\Request diff --git a/app/code/Magento/Webapi/Model/Config/Converter.php b/app/code/Magento/Webapi/Model/Config/Converter.php index ed4ce1e2a7514..9ee9d745c9a45 100644 --- a/app/code/Magento/Webapi/Model/Config/Converter.php +++ b/app/code/Magento/Webapi/Model/Config/Converter.php @@ -16,23 +16,23 @@ class Converter implements \Magento\Framework\Config\ConverterInterface /**#@+ * Array keys for config internal representation. */ - const KEY_SERVICE_CLASS = 'class'; - const KEY_URL = 'url'; - const KEY_SERVICE_METHOD = 'method'; - const KEY_SECURE = 'secure'; - const KEY_ROUTES = 'routes'; - const KEY_ACL_RESOURCES = 'resources'; - const KEY_SERVICE = 'service'; - const KEY_SERVICES = 'services'; - const KEY_FORCE = 'force'; - const KEY_VALUE = 'value'; - const KEY_DATA_PARAMETERS = 'parameters'; - const KEY_SOURCE = 'source'; - const KEY_METHOD = 'method'; - const KEY_METHODS = 'methods'; - const KEY_DESCRIPTION = 'description'; - const KEY_REAL_SERVICE_METHOD = 'realMethod'; - const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; + public const KEY_SERVICE_CLASS = 'class'; + public const KEY_URL = 'url'; + public const KEY_SERVICE_METHOD = 'method'; + public const KEY_SECURE = 'secure'; + public const KEY_ROUTES = 'routes'; + public const KEY_ACL_RESOURCES = 'resources'; + public const KEY_SERVICE = 'service'; + public const KEY_SERVICES = 'services'; + public const KEY_FORCE = 'force'; + public const KEY_VALUE = 'value'; + public const KEY_DATA_PARAMETERS = 'parameters'; + public const KEY_SOURCE = 'source'; + public const KEY_METHOD = 'method'; + public const KEY_METHODS = 'methods'; + public const KEY_DESCRIPTION = 'description'; + public const KEY_REAL_SERVICE_METHOD = 'realMethod'; + public const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /**#@-*/ /** diff --git a/app/code/Magento/Webapi/Model/Rest/Config.php b/app/code/Magento/Webapi/Model/Rest/Config.php index cbed09eef4744..59a7f45eb8abe 100644 --- a/app/code/Magento/Webapi/Model/Rest/Config.php +++ b/app/code/Magento/Webapi/Model/Rest/Config.php @@ -20,23 +20,23 @@ class Config /**#@+ * HTTP methods supported by REST. */ - const HTTP_METHOD_GET = 'GET'; - const HTTP_METHOD_DELETE = 'DELETE'; - const HTTP_METHOD_PUT = 'PUT'; - const HTTP_METHOD_POST = 'POST'; - const HTTP_METHOD_PATCH = 'PATCH'; + public const HTTP_METHOD_GET = 'GET'; + public const HTTP_METHOD_DELETE = 'DELETE'; + public const HTTP_METHOD_PUT = 'PUT'; + public const HTTP_METHOD_POST = 'POST'; + public const HTTP_METHOD_PATCH = 'PATCH'; /**#@-*/ /**#@+ * Keys that a used for config internal representation. */ - const KEY_IS_SECURE = 'isSecure'; - const KEY_CLASS = 'class'; - const KEY_METHOD = 'method'; - const KEY_ROUTE_PATH = 'routePath'; - const KEY_ACL_RESOURCES = 'resources'; - const KEY_PARAMETERS = 'parameters'; - const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; + public const KEY_IS_SECURE = 'isSecure'; + public const KEY_CLASS = 'class'; + public const KEY_METHOD = 'method'; + public const KEY_ROUTE_PATH = 'routePath'; + public const KEY_ACL_RESOURCES = 'resources'; + public const KEY_PARAMETERS = 'parameters'; + public const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /*#@-*/ /** diff --git a/app/code/Magento/Webapi/Model/ServiceMetadata.php b/app/code/Magento/Webapi/Model/ServiceMetadata.php index 99a421508d2ca..fe44f7814b161 100644 --- a/app/code/Magento/Webapi/Model/ServiceMetadata.php +++ b/app/code/Magento/Webapi/Model/ServiceMetadata.php @@ -21,33 +21,33 @@ class ServiceMetadata /**#@+ * Keys that a used for service config internal representation. */ - const KEY_CLASS = 'class'; + public const KEY_CLASS = 'class'; - const KEY_IS_SECURE = 'isSecure'; + public const KEY_IS_SECURE = 'isSecure'; - const KEY_SERVICE_METHODS = 'methods'; + public const KEY_SERVICE_METHODS = 'methods'; - const KEY_METHOD = 'method'; + public const KEY_METHOD = 'method'; - const KEY_IS_REQUIRED = 'inputRequired'; + public const KEY_IS_REQUIRED = 'inputRequired'; - const KEY_ACL_RESOURCES = 'resources'; + public const KEY_ACL_RESOURCES = 'resources'; - const KEY_ROUTES = 'routes'; + public const KEY_ROUTES = 'routes'; - const KEY_ROUTE_METHOD = 'method'; + public const KEY_ROUTE_METHOD = 'method'; - const KEY_ROUTE_PARAMS = 'parameters'; + public const KEY_ROUTE_PARAMS = 'parameters'; - const KEY_METHOD_ALIAS = 'methodAlias'; + public const KEY_METHOD_ALIAS = 'methodAlias'; - const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; + public const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; - const SERVICES_CONFIG_CACHE_ID = 'services-services-config'; + public const SERVICES_CONFIG_CACHE_ID = 'services-services-config'; - const ROUTES_CONFIG_CACHE_ID = 'routes-services-config'; + public const ROUTES_CONFIG_CACHE_ID = 'routes-services-config'; - const REFLECTED_TYPES_CACHE_ID = 'soap-reflected-types'; + public const REFLECTED_TYPES_CACHE_ID = 'soap-reflected-types'; /**#@-*/ diff --git a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php index 593037891d0c8..74a4adb4b1819 100644 --- a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php +++ b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php @@ -16,12 +16,12 @@ class Converter implements \Magento\Framework\Config\ConverterInterface /**#@+ * Array keys for config internal representation. */ - const KEY_SERVICES = 'services'; - const KEY_METHOD = 'method'; - const KEY_METHODS = 'methods'; - const KEY_SYNCHRONOUS_INVOCATION_ONLY = 'synchronousInvocationOnly'; - const KEY_ROUTES = 'routes'; - const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; + public const KEY_SERVICES = 'services'; + public const KEY_METHOD = 'method'; + public const KEY_METHODS = 'methods'; + public const KEY_SYNCHRONOUS_INVOCATION_ONLY = 'synchronousInvocationOnly'; + public const KEY_ROUTES = 'routes'; + public const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit'; /**#@-*/ /** diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index 7d21b53b45a8b..f5f3e0b583823 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -18,151 +18,151 @@ class ConfigOptionsListConstants /**#@+ * Path to the values in the deployment config */ - const CONFIG_PATH_INSTALL_DATE = 'install/date'; - const CONFIG_PATH_CRYPT_KEY = 'crypt/key'; - const CONFIG_PATH_SESSION_SAVE = 'session/save'; - const CONFIG_PATH_RESOURCE_DEFAULT_SETUP = 'resource/default_setup/connection'; - const CONFIG_PATH_DB_CONNECTION_DEFAULT_DRIVER_OPTIONS = 'db/connection/default/driver_options'; - const CONFIG_PATH_DB_CONNECTION_DEFAULT = 'db/connection/default'; - const CONFIG_PATH_DB_CONNECTIONS = 'db/connection'; - const CONFIG_PATH_DB_PREFIX = 'db/table_prefix'; - const CONFIG_PATH_X_FRAME_OPT = 'x-frame-options'; - const CONFIG_PATH_CACHE_HOSTS = 'http_cache_hosts'; - const CONFIG_PATH_BACKEND = 'backend'; - const CONFIG_PATH_INSTALL = 'install'; - const CONFIG_PATH_CRYPT = 'crypt'; - const CONFIG_PATH_SESSION = 'session'; - const CONFIG_PATH_DB = 'db'; - const CONFIG_PATH_RESOURCE = 'resource'; - const CONFIG_PATH_CACHE_TYPES = 'cache_types'; - const CONFIG_PATH_DOCUMENT_ROOT_IS_PUB = 'directories/document_root_is_pub'; - const CONFIG_PATH_DB_LOGGER_OUTPUT = 'db_logger/output'; - const CONFIG_PATH_DB_LOGGER_LOG_EVERYTHING = 'db_logger/log_everything'; - const CONFIG_PATH_DB_LOGGER_QUERY_TIME_THRESHOLD = 'db_logger/query_time_threshold'; - const CONFIG_PATH_DB_LOGGER_INCLUDE_STACKTRACE = 'db_logger/include_stacktrace'; + public const CONFIG_PATH_INSTALL_DATE = 'install/date'; + public const CONFIG_PATH_CRYPT_KEY = 'crypt/key'; + public const CONFIG_PATH_SESSION_SAVE = 'session/save'; + public const CONFIG_PATH_RESOURCE_DEFAULT_SETUP = 'resource/default_setup/connection'; + public const CONFIG_PATH_DB_CONNECTION_DEFAULT_DRIVER_OPTIONS = 'db/connection/default/driver_options'; + public const CONFIG_PATH_DB_CONNECTION_DEFAULT = 'db/connection/default'; + public const CONFIG_PATH_DB_CONNECTIONS = 'db/connection'; + public const CONFIG_PATH_DB_PREFIX = 'db/table_prefix'; + public const CONFIG_PATH_X_FRAME_OPT = 'x-frame-options'; + public const CONFIG_PATH_CACHE_HOSTS = 'http_cache_hosts'; + public const CONFIG_PATH_BACKEND = 'backend'; + public const CONFIG_PATH_INSTALL = 'install'; + public const CONFIG_PATH_CRYPT = 'crypt'; + public const CONFIG_PATH_SESSION = 'session'; + public const CONFIG_PATH_DB = 'db'; + public const CONFIG_PATH_RESOURCE = 'resource'; + public const CONFIG_PATH_CACHE_TYPES = 'cache_types'; + public const CONFIG_PATH_DOCUMENT_ROOT_IS_PUB = 'directories/document_root_is_pub'; + public const CONFIG_PATH_DB_LOGGER_OUTPUT = 'db_logger/output'; + public const CONFIG_PATH_DB_LOGGER_LOG_EVERYTHING = 'db_logger/log_everything'; + public const CONFIG_PATH_DB_LOGGER_QUERY_TIME_THRESHOLD = 'db_logger/query_time_threshold'; + public const CONFIG_PATH_DB_LOGGER_INCLUDE_STACKTRACE = 'db_logger/include_stacktrace'; /**#@-*/ /** * Parameter for disabling/enabling static content deployment on demand in production mode * Can contains 0/1 value */ - const CONFIG_PATH_SCD_ON_DEMAND_IN_PRODUCTION = 'static_content_on_demand_in_production'; + public const CONFIG_PATH_SCD_ON_DEMAND_IN_PRODUCTION = 'static_content_on_demand_in_production'; /** * Parameter for forcing HTML minification even if file is already minified. */ - const CONFIG_PATH_FORCE_HTML_MINIFICATION = 'force_html_minification'; + public const CONFIG_PATH_FORCE_HTML_MINIFICATION = 'force_html_minification'; /** * Default limiting input array size for synchronous Web API */ - const CONFIG_PATH_WEBAPI_SYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT = 'webapi/sync/default_input_array_size_limit'; + public const CONFIG_PATH_WEBAPI_SYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT = 'webapi/sync/default_input_array_size_limit'; /** * Default limiting input array size for asynchronous Web API */ - const CONFIG_PATH_WEBAPI_ASYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT = 'webapi/async/default_input_array_size_limit'; + public const CONFIG_PATH_WEBAPI_ASYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT = 'webapi/async/default_input_array_size_limit'; /**#@+ * Input keys for the options */ - const INPUT_KEY_ENCRYPTION_KEY = 'key'; - const INPUT_KEY_SESSION_SAVE = 'session-save'; - const INPUT_KEY_DB_HOST = 'db-host'; - const INPUT_KEY_DB_NAME = 'db-name'; - const INPUT_KEY_DB_USER = 'db-user'; - const INPUT_KEY_DB_PASSWORD = 'db-password'; - const INPUT_KEY_DB_PREFIX = 'db-prefix'; - const INPUT_KEY_DB_MODEL = 'db-model'; - const INPUT_KEY_DB_INIT_STATEMENTS = 'db-init-statements'; - const INPUT_KEY_DB_ENGINE = 'db-engine'; - const INPUT_KEY_DB_SSL_KEY = 'db-ssl-key'; - const INPUT_KEY_DB_SSL_CERT = 'db-ssl-cert'; - const INPUT_KEY_DB_SSL_CA = 'db-ssl-ca'; - const INPUT_KEY_DB_SSL_VERIFY = 'db-ssl-verify'; - const INPUT_KEY_RESOURCE = 'resource'; - const INPUT_KEY_SKIP_DB_VALIDATION = 'skip-db-validation'; - const INPUT_KEY_CACHE_HOSTS = 'http-cache-hosts'; + public const INPUT_KEY_ENCRYPTION_KEY = 'key'; + public const INPUT_KEY_SESSION_SAVE = 'session-save'; + public const INPUT_KEY_DB_HOST = 'db-host'; + public const INPUT_KEY_DB_NAME = 'db-name'; + public const INPUT_KEY_DB_USER = 'db-user'; + public const INPUT_KEY_DB_PASSWORD = 'db-password'; + public const INPUT_KEY_DB_PREFIX = 'db-prefix'; + public const INPUT_KEY_DB_MODEL = 'db-model'; + public const INPUT_KEY_DB_INIT_STATEMENTS = 'db-init-statements'; + public const INPUT_KEY_DB_ENGINE = 'db-engine'; + public const INPUT_KEY_DB_SSL_KEY = 'db-ssl-key'; + public const INPUT_KEY_DB_SSL_CERT = 'db-ssl-cert'; + public const INPUT_KEY_DB_SSL_CA = 'db-ssl-ca'; + public const INPUT_KEY_DB_SSL_VERIFY = 'db-ssl-verify'; + public const INPUT_KEY_RESOURCE = 'resource'; + public const INPUT_KEY_SKIP_DB_VALIDATION = 'skip-db-validation'; + public const INPUT_KEY_CACHE_HOSTS = 'http-cache-hosts'; /**#@-*/ /**#@+ * Input keys for cache configuration */ - const KEY_CACHE_FRONTEND = 'cache/frontend'; - const CONFIG_PATH_BACKEND_OPTIONS = 'backend_options'; + public const KEY_CACHE_FRONTEND = 'cache/frontend'; + public const CONFIG_PATH_BACKEND_OPTIONS = 'backend_options'; /** * Definition format constant. */ - const INPUT_KEY_DEFINITION_FORMAT = 'definition-format'; + public const INPUT_KEY_DEFINITION_FORMAT = 'definition-format'; /**#@+ * Values for session-save */ - const SESSION_SAVE_FILES = 'files'; - const SESSION_SAVE_DB = 'db'; - const SESSION_SAVE_REDIS = 'redis'; + public const SESSION_SAVE_FILES = 'files'; + public const SESSION_SAVE_DB = 'db'; + public const SESSION_SAVE_REDIS = 'redis'; /**#@-*/ /** * Array Key for session save method */ - const KEY_SAVE = 'save'; + public const KEY_SAVE = 'save'; /**#@+ * Array keys for Database configuration */ - const KEY_HOST = 'host'; - const KEY_PORT = 'port'; - const KEY_NAME = 'dbname'; - const KEY_USER = 'username'; - const KEY_PASSWORD = 'password'; - const KEY_ENGINE = 'engine'; - const KEY_PREFIX = 'table_prefix'; - const KEY_MODEL = 'model'; - const KEY_INIT_STATEMENTS = 'initStatements'; - const KEY_ACTIVE = 'active'; - const KEY_DRIVER_OPTIONS = 'driver_options'; + public const KEY_HOST = 'host'; + public const KEY_PORT = 'port'; + public const KEY_NAME = 'dbname'; + public const KEY_USER = 'username'; + public const KEY_PASSWORD = 'password'; + public const KEY_ENGINE = 'engine'; + public const KEY_PREFIX = 'table_prefix'; + public const KEY_MODEL = 'model'; + public const KEY_INIT_STATEMENTS = 'initStatements'; + public const KEY_ACTIVE = 'active'; + public const KEY_DRIVER_OPTIONS = 'driver_options'; /**#@-*/ /**#@+ * Array keys for database driver options configurations */ - const KEY_MYSQL_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; - const KEY_MYSQL_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; - const KEY_MYSQL_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; + public const KEY_MYSQL_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; + public const KEY_MYSQL_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; + public const KEY_MYSQL_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; /** * Constant \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT cannot be used as it was introduced in PHP 7.1.4 * and Magento 2 is currently supporting PHP 7.1.3. */ - const KEY_MYSQL_SSL_VERIFY = 1014; + public const KEY_MYSQL_SSL_VERIFY = 1014; /**#@-*/ /** * Db config key */ - const KEY_DB = 'db'; + public const KEY_DB = 'db'; /** * Array Key for encryption key in deployment config file */ - const KEY_ENCRYPTION_KEY = 'key'; + public const KEY_ENCRYPTION_KEY = 'key'; /** * Resource config key */ - const KEY_RESOURCE = 'resource'; + public const KEY_RESOURCE = 'resource'; /** * Key for modules */ - const KEY_MODULES = 'modules'; + public const KEY_MODULES = 'modules'; /** * Size of random string generated for store's encryption key * phpcs:disable */ - const STORE_KEY_RANDOM_STRING_SIZE = SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES; + public const STORE_KEY_RANDOM_STRING_SIZE = SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES; //phpcs:enable } diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 8abefd0449ad0..2314fae10f09b 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -36,7 +36,7 @@ */ class ServiceInputProcessor implements ServicePayloadConverterInterface { - const EXTENSION_ATTRIBUTES_TYPE = \Magento\Framework\Api\ExtensionAttributesInterface::class; + public const EXTENSION_ATTRIBUTES_TYPE = \Magento\Framework\Api\ExtensionAttributesInterface::class; /** * @var \Magento\Framework\Reflection\TypeProcessor diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php index 22f4c4ee349b6..85882129da073 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php @@ -11,8 +11,8 @@ class TestService { - const DEFAULT_VALUE = 42; - const CUSTOM_ATTRIBUTE_CODE = 'customAttr'; + public const DEFAULT_VALUE = 42; + public const CUSTOM_ATTRIBUTE_CODE = 'customAttr'; /** * @param int $entityId From 4eb2cc7307ec7ef043bc93fad2aba7b2f6fecca3 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 1 Dec 2021 15:16:51 -0600 Subject: [PATCH 30/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Magento/Webapi/Controller/Rest/InputParamsResolver.php | 6 +++--- .../Magento/TestFramework/TestCase/WebapiAbstract.php | 3 +++ .../Magento/Framework/Config/ConfigOptionsListConstants.php | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php index 87fdb053a6665..505388963d048 100644 --- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php +++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php @@ -184,9 +184,9 @@ private function validateParameters( } } if (!empty($paramOverriders)) { - throw new \UnexpectedValueException( - __('The current request does not expect the next parameters: ' . implode(', ', $paramOverriders)) - ); + $message = 'The current request does not expect the next parameters: ' + . implode(', ', $paramOverriders); + throw new \UnexpectedValueException(__($message)->__toString()); } } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 7ccab097d7778..9089d3ede45b1 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -566,6 +566,9 @@ protected function _restoreAppConfig() public function processRestExceptionResult(\Exception $e) { $error = json_decode($e->getMessage(), true); + if(json_last_error() !== JSON_ERROR_NONE) { + $error['message'] = $e->getMessage(); + } //Remove line breaks and replace with space $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message'])); // remove trace and type, will only be present if server is in dev mode diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index f5f3e0b583823..670c74dd197bc 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -60,8 +60,10 @@ class ConfigOptionsListConstants /** * Default limiting input array size for asynchronous Web API + * phpcs:disable */ public const CONFIG_PATH_WEBAPI_ASYNC_DEFAULT_INPUT_ARRAY_SIZE_LIMIT = 'webapi/async/default_input_array_size_limit'; + //phpcs:enable /**#@+ * Input keys for the options From d7de9481f4df24b98e1ebf31d7ab108730257455 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 1 Dec 2021 18:28:28 -0600 Subject: [PATCH 31/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../TestFramework/TestCase/WebapiAbstract.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 9089d3ede45b1..2dd022cc211f5 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -23,16 +23,16 @@ abstract class WebapiAbstract extends \PHPUnit\Framework\TestCase /**#@+ * Auto tear down options in setFixture */ - const AUTO_TEAR_DOWN_DISABLED = 0; - const AUTO_TEAR_DOWN_AFTER_METHOD = 1; - const AUTO_TEAR_DOWN_AFTER_CLASS = 2; + public const AUTO_TEAR_DOWN_DISABLED = 0; + public const AUTO_TEAR_DOWN_AFTER_METHOD = 1; + public const AUTO_TEAR_DOWN_AFTER_CLASS = 2; /**#@-*/ /**#@+ * Web API adapters that are used to perform actual calls. */ - const ADAPTER_SOAP = 'soap'; - const ADAPTER_REST = 'rest'; + public const ADAPTER_SOAP = 'soap'; + public const ADAPTER_REST = 'rest'; /**#@-*/ /** @@ -566,7 +566,7 @@ protected function _restoreAppConfig() public function processRestExceptionResult(\Exception $e) { $error = json_decode($e->getMessage(), true); - if(json_last_error() !== JSON_ERROR_NONE) { + if (json_last_error() !== JSON_ERROR_NONE) { $error['message'] = $e->getMessage(); } //Remove line breaks and replace with space From ae8fafc6990d4d9e5abb77ec3e4ead697f754c84 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 7 Dec 2021 19:32:26 -0600 Subject: [PATCH 32/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../Plugin/BearerTokenValidatorPlugin.php | 49 +++++++++++++ .../Plugin/BearerTokenValidatorPluginTest.php | 72 +++++++++++++++++++ app/code/Magento/Analytics/etc/di.xml | 3 + .../Model/Config/AuthorizationConfig.php | 2 +- .../Integration/Model/OpaqueToken/Reader.php | 33 +++++---- .../Model/Validator/BearerTokenValidator.php | 43 +++++++++++ .../Model/Authorization/SoapUserContext.php | 28 ++++---- 7 files changed, 200 insertions(+), 30 deletions(-) create mode 100644 app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php create mode 100644 app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php create mode 100644 app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php diff --git a/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php b/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php new file mode 100644 index 0000000000000..e6bc1b63b5c91 --- /dev/null +++ b/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php @@ -0,0 +1,49 @@ +config = $config; + } + + /*** + * Always allow access token for analytics to be used as bearer + * + * @param BearerTokenValidator $subject + * @param bool $result + * @param Integration $integration + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterIsIntegrationAllowedToAsBearerToken( + BearerTokenValidator $subject, + bool $result, + Integration $integration + ): bool { + return $result || $integration->getName() === $this->config->getValue('analytics/integration_name'); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php b/app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php new file mode 100644 index 0000000000000..f41cdd0b940f7 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php @@ -0,0 +1,72 @@ +createMock(ScopeConfigInterface::class); + $config->method('getValue') + ->with('analytics/integration_name') + ->willReturn('abc'); + $this->plugin = new BearerTokenValidatorPlugin($config); + $this->validator = $this->createMock(BearerTokenValidator::class); + } + + public function testTrueIsPassedThrough() + { + $integration = $this->createMock(Integration::class); + $integration->method('__call') + ->with('getName') + ->willReturn('invalid'); + + $result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, true, $integration); + self::assertTrue($result); + } + + public function testFalseWhenIntegrationDoesntMatch() + { + $integration = $this->createMock(Integration::class); + $integration->method('__call') + ->with('getName') + ->willReturn('invalid'); + + $result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, false, $integration); + self::assertFalse($result); + } + + public function testTrueWhenIntegrationMatches() + { + $integration = $this->createMock(Integration::class); + $integration->method('__call') + ->with('getName') + ->willReturn('abc'); + + $result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, true, $integration); + self::assertTrue($result); + } +} diff --git a/app/code/Magento/Analytics/etc/di.xml b/app/code/Magento/Analytics/etc/di.xml index 0a57676b5fb8f..3615bd9ca2d32 100644 --- a/app/code/Magento/Analytics/etc/di.xml +++ b/app/code/Magento/Analytics/etc/di.xml @@ -271,4 +271,7 @@ Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactory + + + diff --git a/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php b/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php index 4103f27b0ce3d..c0485da69afd1 100644 --- a/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php +++ b/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php @@ -24,7 +24,7 @@ class AuthorizationConfig /** * @var ScopeConfigInterface */ - private $scopeConfig; + private ScopeConfigInterface $scopeConfig; /** * @param ScopeConfigInterface $scopeConfig diff --git a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php index 3401653db0e7f..c47d7b06f9312 100644 --- a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php +++ b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php @@ -8,6 +8,7 @@ namespace Magento\Integration\Model\OpaqueToken; +use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\App\ObjectManager; use Magento\Integration\Api\Data\UserToken; use Magento\Integration\Api\Exception\UserTokenException; @@ -18,6 +19,7 @@ use Magento\Integration\Model\Oauth\Token; use Magento\Integration\Model\Oauth\TokenFactory; use Magento\Integration\Helper\Oauth\Data as OauthHelper; +use Magento\Integration\Model\Validator\BearerTokenValidator; /** * Reads user token data @@ -37,33 +39,33 @@ class Reader implements UserTokenReaderInterface private $helper; /** - * @var AuthorizationConfig + * @var IntegrationServiceInterface */ - private $authorizationConfig; + private IntegrationServiceInterface $integrationService; /** - * @var IntegrationServiceInterface + * @var BearerTokenValidator */ - private IntegrationServiceInterface $integrationService; + private BearerTokenValidator $bearerTokenValidator; /** * @param TokenFactory $tokenFactory * @param OauthHelper $helper - * @param AuthorizationConfig|null $authorizationConfig * @param IntegrationServiceInterface|null $integrationService + * @param BearerTokenValidator|null $bearerTokenValidator */ public function __construct( TokenFactory $tokenFactory, OauthHelper $helper, - ?AuthorizationConfig $authorizationConfig = null, - ?IntegrationServiceInterface $integrationService = null + ?IntegrationServiceInterface $integrationService = null, + ?BearerTokenValidator $bearerTokenValidator = null ) { $this->tokenFactory = $tokenFactory; $this->helper = $helper; - $this->authorizationConfig = $authorizationConfig ?? ObjectManager::getInstance() - ->get(AuthorizationConfig::class); $this->integrationService = $integrationService ?? ObjectManager::getInstance() ->get(IntegrationServiceInterface::class); + $this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance() + ->get(BearerTokenValidator::class); } /** @@ -118,12 +120,9 @@ private function getTokenModel(string $token): Token */ private function validateUserType(int $userType): void { - if ($userType === CustomUserContext::USER_TYPE_INTEGRATION) { - if (!$this->authorizationConfig->isIntegrationAsBearerEnabled()) { - throw new UserTokenException('Invalid token found'); - } - } elseif ($userType !== CustomUserContext::USER_TYPE_ADMIN + if ($userType !== CustomUserContext::USER_TYPE_ADMIN && $userType !== CustomUserContext::USER_TYPE_CUSTOMER + && $userType !== CustomUserContext::USER_TYPE_INTEGRATION ) { throw new UserTokenException('Invalid token found'); } @@ -138,11 +137,15 @@ private function validateUserType(int $userType): void private function getUserId(Token $tokenModel): int { $userType = (int)$tokenModel->getUserType(); + $userId = null; if ($userType === CustomUserContext::USER_TYPE_ADMIN) { $userId = $tokenModel->getAdminId(); } elseif ($userType === CustomUserContext::USER_TYPE_INTEGRATION) { - $userId = $this->integrationService->findByConsumerId($tokenModel->getConsumerId())->getId(); + $integration = $this->integrationService->findByConsumerId($tokenModel->getConsumerId()); + if ($this->bearerTokenValidator->isIntegrationAllowedToAsBearerToken($integration)) { + $userId = $integration->getId(); + } } else { $userId = $tokenModel->getCustomerId(); } diff --git a/app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php b/app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php new file mode 100644 index 0000000000000..46c212d63dc61 --- /dev/null +++ b/app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php @@ -0,0 +1,43 @@ +authorizationConfig = $authorizationConfig; + } + + /** + * Validate an integration's access token can be used as a standalone bearer token + * + * @param Integration $integration + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function isIntegrationAllowedToAsBearerToken(Integration $integration): bool + { + return $this->authorizationConfig->isIntegrationAsBearerEnabled(); + } +} diff --git a/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php index 9754be6ebefe1..f6fc77dab286e 100644 --- a/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php +++ b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php @@ -8,14 +8,11 @@ use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\App\ObjectManager; -use Magento\Integration\Model\Config\AuthorizationConfig; use Magento\Integration\Model\Oauth\Token; use Magento\Integration\Model\Oauth\TokenFactory; use Magento\Integration\Api\IntegrationServiceInterface; use Magento\Framework\Webapi\Request; -use Magento\Framework\Stdlib\DateTime\DateTime as Date; -use Magento\Framework\Stdlib\DateTime; -use Magento\Integration\Helper\Oauth\Data as OauthHelper; +use Magento\Integration\Model\Validator\BearerTokenValidator; /** * SOAP specific user context based on opaque tokens. @@ -50,12 +47,12 @@ class SoapUserContext implements UserContextInterface /** * @var IntegrationServiceInterface */ - private $integrationService; + private IntegrationServiceInterface $integrationService; /** - * @var AuthorizationConfig + * @var BearerTokenValidator */ - private $authorizationConfig; + private BearerTokenValidator $bearerTokenValidator; /** * Initialize dependencies. @@ -63,19 +60,19 @@ class SoapUserContext implements UserContextInterface * @param Request $request * @param TokenFactory $tokenFactory * @param IntegrationServiceInterface $integrationService - * @param AuthorizationConfig|null $authorizationConfig + * @param BearerTokenValidator|null $bearerTokenValidator */ public function __construct( Request $request, TokenFactory $tokenFactory, IntegrationServiceInterface $integrationService, - ?AuthorizationConfig $authorizationConfig = null + ?BearerTokenValidator $bearerTokenValidator = null ) { $this->request = $request; $this->tokenFactory = $tokenFactory; $this->integrationService = $integrationService; - $this->authorizationConfig = $authorizationConfig ?? ObjectManager::getInstance() - ->get(AuthorizationConfig::class); + $this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance() + ->get(BearerTokenValidator::class); } /** @@ -117,7 +114,7 @@ private function processRequest() //phpcs:ignore CopyPaste return; } $tokenType = strtolower($headerPieces[0]); - if ($tokenType !== 'bearer' || !$this->authorizationConfig->isIntegrationAsBearerEnabled()) { + if ($tokenType !== 'bearer') { $this->isRequestProcessed = true; return; } @@ -131,8 +128,11 @@ private function processRequest() //phpcs:ignore CopyPaste return; } if (((int) $token->getUserType()) === UserContextInterface::USER_TYPE_INTEGRATION) { - $this->userId = $this->integrationService->findByConsumerId($token->getConsumerId())->getId(); - $this->userType = UserContextInterface::USER_TYPE_INTEGRATION; + $integration = $this->integrationService->findByConsumerId($token->getConsumerId()); + if ($this->bearerTokenValidator->isIntegrationAllowedToAsBearerToken($integration)) { + $this->userId = $integration->getId(); + $this->userType = UserContextInterface::USER_TYPE_INTEGRATION; + } } $this->isRequestProcessed = true; } From 021670c5b3b18b61b7f43e2418b4e4f2deae1f3a Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 7 Dec 2021 19:34:44 -0600 Subject: [PATCH 33/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php | 2 +- .../Test/Unit/Plugin/BearerTokenValidatorPluginTest.php | 6 +++--- app/code/Magento/Integration/Model/OpaqueToken/Reader.php | 2 +- .../Integration/Model/Validator/BearerTokenValidator.php | 2 +- .../Magento/Webapi/Model/Authorization/SoapUserContext.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php b/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php index e6bc1b63b5c91..3497d2a14fd25 100644 --- a/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php +++ b/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php @@ -39,7 +39,7 @@ public function __construct(ScopeConfigInterface $config) * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterIsIntegrationAllowedToAsBearerToken( + public function afterIsIntegrationAllowedAsBearerToken( BearerTokenValidator $subject, bool $result, Integration $integration diff --git a/app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php b/app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php index f41cdd0b940f7..94b5a7817838c 100644 --- a/app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Plugin/BearerTokenValidatorPluginTest.php @@ -44,7 +44,7 @@ public function testTrueIsPassedThrough() ->with('getName') ->willReturn('invalid'); - $result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, true, $integration); + $result = $this->plugin->afterIsIntegrationAllowedAsBearerToken($this->validator, true, $integration); self::assertTrue($result); } @@ -55,7 +55,7 @@ public function testFalseWhenIntegrationDoesntMatch() ->with('getName') ->willReturn('invalid'); - $result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, false, $integration); + $result = $this->plugin->afterIsIntegrationAllowedAsBearerToken($this->validator, false, $integration); self::assertFalse($result); } @@ -66,7 +66,7 @@ public function testTrueWhenIntegrationMatches() ->with('getName') ->willReturn('abc'); - $result = $this->plugin->afterIsIntegrationAllowedToAsBearerToken($this->validator, true, $integration); + $result = $this->plugin->afterIsIntegrationAllowedAsBearerToken($this->validator, true, $integration); self::assertTrue($result); } } diff --git a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php index c47d7b06f9312..c0b318a563a27 100644 --- a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php +++ b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php @@ -143,7 +143,7 @@ private function getUserId(Token $tokenModel): int $userId = $tokenModel->getAdminId(); } elseif ($userType === CustomUserContext::USER_TYPE_INTEGRATION) { $integration = $this->integrationService->findByConsumerId($tokenModel->getConsumerId()); - if ($this->bearerTokenValidator->isIntegrationAllowedToAsBearerToken($integration)) { + if ($this->bearerTokenValidator->isIntegrationAllowedAsBearerToken($integration)) { $userId = $integration->getId(); } } else { diff --git a/app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php b/app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php index 46c212d63dc61..a18cc64d891a0 100644 --- a/app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php +++ b/app/code/Magento/Integration/Model/Validator/BearerTokenValidator.php @@ -36,7 +36,7 @@ public function __construct(AuthorizationConfig $authorizationConfig) * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function isIntegrationAllowedToAsBearerToken(Integration $integration): bool + public function isIntegrationAllowedAsBearerToken(Integration $integration): bool { return $this->authorizationConfig->isIntegrationAsBearerEnabled(); } diff --git a/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php index f6fc77dab286e..d225f0d96546e 100644 --- a/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php +++ b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php @@ -129,7 +129,7 @@ private function processRequest() //phpcs:ignore CopyPaste } if (((int) $token->getUserType()) === UserContextInterface::USER_TYPE_INTEGRATION) { $integration = $this->integrationService->findByConsumerId($token->getConsumerId()); - if ($this->bearerTokenValidator->isIntegrationAllowedToAsBearerToken($integration)) { + if ($this->bearerTokenValidator->isIntegrationAllowedAsBearerToken($integration)) { $this->userId = $integration->getId(); $this->userType = UserContextInterface::USER_TYPE_INTEGRATION; } From 0446a57632e00775e0cd4f2bb6bdab7d1b5a6a48 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 8 Dec 2021 09:19:55 -0600 Subject: [PATCH 34/37] AC-1619: Integration access tokens do not work as Bearer tokens --- .../Magento/Integration/Model/Config/AuthorizationConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php b/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php index c0485da69afd1..7cbd4ee3e8306 100644 --- a/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php +++ b/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php @@ -19,7 +19,7 @@ class AuthorizationConfig /** * XML Path for Enable Integration as Bearer */ - const CONFIG_PATH_INTEGRATION_BEARER = 'oauth/consumer/enable_integration_as_bearer'; + private const CONFIG_PATH_INTEGRATION_BEARER = 'oauth/consumer/enable_integration_as_bearer'; /** * @var ScopeConfigInterface From 34b9ae7e2d5ce453d7cacb53e19d09bd7d2f7066 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 8 Dec 2021 15:45:21 -0600 Subject: [PATCH 35/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Controller/Rest/InputParamsResolver.php | 14 ++++++- .../Controller/Soap/Request/Handler.php | 31 ++++++++++---- .../Rest/Asynchronous/InputParamsResolver.php | 42 +++++++++++++------ .../Webapi/ServiceInputProcessor.php | 16 +------ 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php index 505388963d048..5197a56b3a7f1 100644 --- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php +++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php @@ -17,6 +17,7 @@ use Magento\Framework\Webapi\Exception; use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Rest\Request as RestRequest; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\Router\Route; /** @@ -60,6 +61,11 @@ class InputParamsResolver */ private $methodsMap; + /** + * @var InputArraySizeLimitValue + */ + private $inputArraySizeLimitValue; + /** * Initialize dependencies * @@ -69,6 +75,7 @@ class InputParamsResolver * @param Router $router * @param RequestValidator $requestValidator * @param MethodsMap|null $methodsMap + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( RestRequest $request, @@ -76,7 +83,8 @@ public function __construct( ServiceInputProcessor $serviceInputProcessor, Router $router, RequestValidator $requestValidator, - MethodsMap $methodsMap = null + MethodsMap $methodsMap = null, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; @@ -85,6 +93,8 @@ public function __construct( $this->requestValidator = $requestValidator; $this->methodsMap = $methodsMap ?: ObjectManager::getInstance() ->get(MethodsMap::class); + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); } /** @@ -97,12 +107,12 @@ public function resolve() { $this->requestValidator->validate(); $route = $this->getRoute(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); return $this->serviceInputProcessor->process( $route->getServiceClass(), $route->getServiceMethod(), $this->getInputData(), - $route->getInputArraySizeLimit() ); } diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index 9b7619b005bd0..ef1d33d2beccb 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -8,16 +8,19 @@ namespace Magento\Webapi\Controller\Soap\Request; +use InvalidArgumentException; use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Framework\Api\MetadataObjectInterface; use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; +use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Webapi\Authorization; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Request as SoapRequest; use Magento\Framework\Webapi\Exception as WebapiException; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Model\Soap\Config as SoapConfig; use Magento\Framework\Reflection\MethodsMap; @@ -40,7 +43,7 @@ class Handler protected $_request; /** - * @var \Magento\Framework\ObjectManagerInterface + * @var ObjectManagerInterface */ protected $_objectManager; @@ -79,11 +82,16 @@ class Handler */ private $paramsOverrider; + /** + * @var InputArraySizeLimitValue + */ + private $inputArraySizeLimitValue; + /** * Initialize dependencies. * * @param SoapRequest $request - * @param \Magento\Framework\ObjectManagerInterface $objectManager + * @param ObjectManagerInterface $objectManager * @param SoapConfig $apiConfig * @param Authorization $authorization * @param SimpleDataObjectConverter $dataObjectConverter @@ -91,17 +99,19 @@ class Handler * @param DataObjectProcessor $dataObjectProcessor * @param MethodsMap $methodsMapProcessor * @param ParamsOverrider|null $paramsOverrider + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( SoapRequest $request, - \Magento\Framework\ObjectManagerInterface $objectManager, + ObjectManagerInterface $objectManager, SoapConfig $apiConfig, Authorization $authorization, SimpleDataObjectConverter $dataObjectConverter, ServiceInputProcessor $serviceInputProcessor, DataObjectProcessor $dataObjectProcessor, MethodsMap $methodsMapProcessor, - ?ParamsOverrider $paramsOverrider = null + ?ParamsOverrider $paramsOverrider = null, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->_request = $request; $this->_objectManager = $objectManager; @@ -112,6 +122,8 @@ public function __construct( $this->_dataObjectProcessor = $dataObjectProcessor; $this->methodsMapProcessor = $methodsMapProcessor; $this->paramsOverrider = $paramsOverrider ?? ObjectManager::getInstance()->get(ParamsOverrider::class); + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); } /** @@ -179,12 +191,12 @@ private function prepareOperationInput(string $serviceClass, array $methodMetada $arguments = reset($arguments); $arguments = $this->_dataObjectConverter->convertStdObjectToArray($arguments, true); $arguments = $this->paramsOverrider->override($arguments, $methodMetadata[ServiceMetadata::KEY_ROUTE_PARAMS]); + $this->inputArraySizeLimitValue->set($methodMetadata[ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT]); return $this->serviceInputProcessor->process( $serviceClass, $methodMetadata[ServiceMetadata::KEY_METHOD], - $arguments, - $methodMetadata[ServiceMetadata::KEY_INPUT_ARRAY_SIZE_LIMIT] + $arguments ); } @@ -195,8 +207,9 @@ private function prepareOperationInput(string $serviceClass, array $methodMetada * @param string $serviceMethod * @param array $arguments * @return array - * @deprecated 100.3.2 + * @throws WebapiException * @see Handler::prepareOperationInput() + * @deprecated 100.3.2 */ protected function _prepareRequestData($serviceClass, $serviceMethod, $arguments) { @@ -214,7 +227,7 @@ protected function _prepareRequestData($serviceClass, $serviceMethod, $arguments * @param string $serviceClassName * @param string $serviceMethodName * @return array - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function _prepareResponseData($data, $serviceClassName, $serviceMethodName) { @@ -241,7 +254,7 @@ protected function _prepareResponseData($data, $serviceClassName, $serviceMethod } elseif (is_scalar($data) || $data === null) { $result = $data; } else { - throw new \InvalidArgumentException("Service returned result in invalid format."); + throw new InvalidArgumentException("Service returned result in invalid format."); } return [self::RESULT_NODE_NAME => $result]; } diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index f49b03aef7aaf..2c6eae525414d 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,11 +8,14 @@ namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Webapi\Exception; use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; +use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Controller\Rest\RequestValidator; @@ -53,6 +56,11 @@ class InputParamsResolver */ private $isBulk; + /** + * @var InputArraySizeLimitValue|null + */ + private $inputArraySizeLimitValue; + /** * Initialize dependencies. * @@ -62,6 +70,7 @@ class InputParamsResolver * @param Router $router * @param RequestValidator $requestValidator * @param WebapiInputParamsResolver $inputParamsResolver + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue * @param bool $isBulk */ public function __construct( @@ -71,7 +80,8 @@ public function __construct( Router $router, RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, - $isBulk = false + bool $isBulk = false, + ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; @@ -80,6 +90,8 @@ public function __construct( $this->requestValidator = $requestValidator; $this->inputParamsResolver = $inputParamsResolver; $this->isBulk = $isBulk; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); } /** @@ -91,20 +103,29 @@ public function __construct( * @return array * @throws InputException if no value is provided for required parameters * @throws Exception - * @throws AuthorizationException + * @throws AuthorizationException|LocalizedException */ public function resolve() { if ($this->isBulk === false) { return [$this->inputParamsResolver->resolve()]; } + $this->requestValidator->validate(); $webapiResolvedParams = []; $route = $this->getRoute(); + $routeServiceClass = $route->getServiceClass(); + $routeServiceMethod = $route->getServiceMethod(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); foreach ($this->getInputData() as $key => $singleEntityParams) { - $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams, $route); + $webapiResolvedParams[$key] = $this->resolveBulkItemParams( + $singleEntityParams, + $routeServiceClass, + $routeServiceMethod + ); } + return $webapiResolvedParams; } @@ -132,6 +153,7 @@ public function getInputData() * Returns route. * * @return Route + * @throws Exception */ public function getRoute() { @@ -148,17 +170,13 @@ public function getRoute() * we don't need to merge body params with url params and use only body params * * @param array $inputData data to send to method in key-value format - * @param Route $route + * @param string $serviceClass route Service Class + * @param string $serviceMethod route Service Method * @return array list of parameters that can be used to call the service method - * @throws Exception + * @throws Exception|LocalizedException */ - private function resolveBulkItemParams(array $inputData, Route $route): array + private function resolveBulkItemParams(array $inputData, string $serviceClass, string $serviceMethod): array { - return $this->serviceInputProcessor->process( - $route->getServiceClass(), - $route->getServiceMethod(), - $inputData, - $route->getInputArraySizeLimit() - ); + return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } } diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 2314fae10f09b..6710923c889e6 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -23,7 +23,6 @@ use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; use Laminas\Code\Reflection\ClassReflection; -use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Framework\Webapi\Validator\IOLimit\DefaultPageSizeSetter; use Magento\Framework\Webapi\Validator\ServiceInputValidatorInterface; @@ -103,11 +102,6 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface */ private $defaultPageSizeSetter; - /** - * @var InputArraySizeLimitValue - */ - private $inputArraySizeLimitValue; - /** * Initialize dependencies. * @@ -122,7 +116,6 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param ServiceInputValidatorInterface|null $serviceInputValidator * @param int $defaultPageSize * @param DefaultPageSizeSetter|null $defaultPageSizeSetter - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -136,8 +129,7 @@ public function __construct( array $customAttributePreprocessors = [], ServiceInputValidatorInterface $serviceInputValidator = null, int $defaultPageSize = 20, - ?DefaultPageSizeSetter $defaultPageSizeSetter = null, - ?InputArraySizeLimitValue $inputArraySizeLimitValue = null + ?DefaultPageSizeSetter $defaultPageSizeSetter = null ) { $this->typeProcessor = $typeProcessor; $this->objectManager = $objectManager; @@ -154,8 +146,6 @@ public function __construct( $this->defaultPageSize = $defaultPageSize >= 10 ? $defaultPageSize : 10; $this->defaultPageSizeSetter = $defaultPageSizeSetter ?? ObjectManager::getInstance() ->get(DefaultPageSizeSetter::class); - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); } /** @@ -186,16 +176,14 @@ private function getNameFinder() * @param string $serviceClassName name of the service class that we are trying to call * @param string $serviceMethodName name of the method that we are trying to call * @param array $inputArray data to send to method in key-value format - * @param int|null $inputArraySizeLimit size limit for input array * @return array list of parameters that can be used to call the service method * @throws Exception * @throws LocalizedException */ - public function process($serviceClassName, $serviceMethodName, array $inputArray, ?int $inputArraySizeLimit = null) + public function process($serviceClassName, $serviceMethodName, array $inputArray) { $inputData = []; $inputError = []; - $this->inputArraySizeLimitValue->set($inputArraySizeLimit); foreach ($this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName) as $param) { $paramName = $param[MethodsMap::METHOD_META_NAME]; From 613a739496b8392a8450feb9a1b113d996d4ae75 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Wed, 8 Dec 2021 18:17:57 -0600 Subject: [PATCH 36/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Controller/Soap/Request/Handler.php | 21 ++++++++++--------- .../Rest/Asynchronous/InputParamsResolver.php | 2 +- .../Test/Unit/ServiceInputProcessorTest.php | 12 +++-------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index ef1d33d2beccb..2c54b21624811 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -18,7 +18,7 @@ use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Webapi\ServiceInputProcessor; -use Magento\Framework\Webapi\Request as SoapRequest; +use Magento\Framework\Webapi\Request as WebapiRequest; use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue; use Magento\Webapi\Controller\Rest\ParamsOverrider; @@ -38,7 +38,7 @@ class Handler public const RESULT_NODE_NAME = 'result'; /** - * @var \Magento\Framework\Webapi\Request + * @var WebapiRequest */ protected $_request; @@ -48,32 +48,32 @@ class Handler protected $_objectManager; /** - * @var \Magento\Webapi\Model\Soap\Config + * @var SoapConfig */ protected $_apiConfig; /** - * @var \Magento\Framework\Webapi\Authorization + * @var Authorization */ protected $authorization; /** - * @var \Magento\Framework\Api\SimpleDataObjectConverter + * @var SimpleDataObjectConverter */ protected $_dataObjectConverter; /** - * @var \Magento\Framework\Webapi\ServiceInputProcessor + * @var ServiceInputProcessor */ protected $serviceInputProcessor; /** - * @var \Magento\Framework\Reflection\DataObjectProcessor + * @var DataObjectProcessor */ protected $_dataObjectProcessor; /** - * @var \Magento\Framework\Reflection\MethodsMap + * @var MethodsMap */ protected $methodsMapProcessor; @@ -90,7 +90,7 @@ class Handler /** * Initialize dependencies. * - * @param SoapRequest $request + * @param WebapiRequest $request * @param ObjectManagerInterface $objectManager * @param SoapConfig $apiConfig * @param Authorization $authorization @@ -100,9 +100,10 @@ class Handler * @param MethodsMap $methodsMapProcessor * @param ParamsOverrider|null $paramsOverrider * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - SoapRequest $request, + WebapiRequest $request, ObjectManagerInterface $objectManager, SoapConfig $apiConfig, Authorization $authorization, diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index 2c6eae525414d..8601e5011bda7 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -70,8 +70,8 @@ class InputParamsResolver * @param Router $router * @param RequestValidator $requestValidator * @param WebapiInputParamsResolver $inputParamsResolver - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue * @param bool $isBulk + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue */ public function __construct( RestRequest $request, diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php index 0a07ad43771c9..ff1c538402133 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php @@ -84,11 +84,6 @@ class ServiceInputProcessorTest extends TestCase */ private $defaultPageSizeSetter; - /** - * @var InputArraySizeLimitValue - */ - private $inputArraySizeLimitValue; - /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -173,7 +168,7 @@ function () use ($objectManager) { ->disableOriginalConstructor() ->getMock(); - $this->inputArraySizeLimitValue = $this->createMock(InputArraySizeLimitValue::class); + $inputArraySizeLimitValue = $this->createMock(InputArraySizeLimitValue::class); $this->defaultPageSizeSetter = self::getMockBuilder(DefaultPageSizeSetter::class) ->disableOriginalConstructor() @@ -191,11 +186,10 @@ function () use ($objectManager) { 'serviceInputValidator' => new EntityArrayValidator( 50, $this->inputLimitConfig, - $this->inputArraySizeLimitValue + $inputArraySizeLimitValue ), 'defaultPageSizeSetter' => $this->defaultPageSizeSetter, - 'defaultPageSize' => 123, - 'inputArraySizeLimitValue' => $this->inputArraySizeLimitValue + 'defaultPageSize' => 123 ] ); From 0c8c6a4d484ea3f4aa1a577e7904546c58c799c7 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko Date: Mon, 20 Dec 2021 17:40:17 -0600 Subject: [PATCH 37/37] AC-465: Allow to configure input limit for RESTful endpoints --- .../Framework/Webapi/ServiceInputProcessor.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 6710923c889e6..908a4e701406f 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -13,7 +13,6 @@ use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\InputException; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\SerializationException; use Magento\Framework\ObjectManager\ConfigInterface; use Magento\Framework\ObjectManagerInterface; @@ -98,7 +97,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface private $defaultPageSize; /** - * @var DefaultPageSizeSetter + * @var DefaultPageSizeSetter|null */ private $defaultPageSizeSetter; @@ -177,19 +176,19 @@ private function getNameFinder() * @param string $serviceMethodName name of the method that we are trying to call * @param array $inputArray data to send to method in key-value format * @return array list of parameters that can be used to call the service method - * @throws Exception - * @throws LocalizedException + * @throws WebapiException */ public function process($serviceClassName, $serviceMethodName, array $inputArray) { $inputData = []; $inputError = []; - foreach ($this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName) as $param) { $paramName = $param[MethodsMap::METHOD_META_NAME]; $snakeCaseParamName = strtolower(preg_replace("/(?<=\\w)(?=[A-Z])/", "_$1", $paramName)); if (isset($inputArray[$paramName]) || isset($inputArray[$snakeCaseParamName])) { - $paramValue = $inputArray[$paramName] ?? $inputArray[$snakeCaseParamName]; + $paramValue = isset($inputArray[$paramName]) + ? $inputArray[$paramName] + : $inputArray[$snakeCaseParamName]; try { $inputData[] = $this->convertValue($paramValue, $param[MethodsMap::METHOD_META_TYPE]); @@ -204,9 +203,7 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray } } } - $this->processInputError($inputError); - return $inputData; } @@ -217,7 +214,7 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray * @param array $data * @return array * @throws \ReflectionException - * @throws LocalizedException + * @throws \Magento\Framework\Exception\LocalizedException */ private function getConstructorData(string $className, array $data): array { @@ -491,7 +488,7 @@ protected function _createDataObjectForTypeAndArrayValue($type, $customAttribute * @param mixed $data * @param string $type Convert given value to the this type * @return mixed - * @throws LocalizedException + * @throws \Magento\Framework\Exception\LocalizedException */ public function convertValue($data, $type) {