diff --git a/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php b/app/code/Magento/Analytics/Plugin/BearerTokenValidatorPlugin.php
new file mode 100644
index 0000000000000..3497d2a14fd25
--- /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 afterIsIntegrationAllowedAsBearerToken(
+ 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..94b5a7817838c
--- /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->afterIsIntegrationAllowedAsBearerToken($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->afterIsIntegrationAllowedAsBearerToken($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->afterIsIntegrationAllowedAsBearerToken($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/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/Config/AuthorizationConfig.php b/app/code/Magento/Integration/Model/Config/AuthorizationConfig.php
new file mode 100644
index 0000000000000..7cbd4ee3e8306
--- /dev/null
+++ b/app/code/Magento/Integration/Model/Config/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/Integration/Model/OpaqueToken/Reader.php b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php
index fedf613188840..c0b318a563a27 100644
--- a/app/code/Magento/Integration/Model/OpaqueToken/Reader.php
+++ b/app/code/Magento/Integration/Model/OpaqueToken/Reader.php
@@ -8,14 +8,24 @@
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;
+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\Integration\Model\Validator\BearerTokenValidator;
+/**
+ * Reads user token data
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class Reader implements UserTokenReaderInterface
{
/**
@@ -28,14 +38,34 @@ class Reader implements UserTokenReaderInterface
*/
private $helper;
+ /**
+ * @var IntegrationServiceInterface
+ */
+ private IntegrationServiceInterface $integrationService;
+
+ /**
+ * @var BearerTokenValidator
+ */
+ private BearerTokenValidator $bearerTokenValidator;
+
/**
* @param TokenFactory $tokenFactory
* @param OauthHelper $helper
+ * @param IntegrationServiceInterface|null $integrationService
+ * @param BearerTokenValidator|null $bearerTokenValidator
*/
- public function __construct(TokenFactory $tokenFactory, OauthHelper $helper)
- {
+ public function __construct(
+ TokenFactory $tokenFactory,
+ OauthHelper $helper,
+ ?IntegrationServiceInterface $integrationService = null,
+ ?BearerTokenValidator $bearerTokenValidator = null
+ ) {
$this->tokenFactory = $tokenFactory;
$this->helper = $helper;
+ $this->integrationService = $integrationService ?? ObjectManager::getInstance()
+ ->get(IntegrationServiceInterface::class);
+ $this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance()
+ ->get(BearerTokenValidator::class);
}
/**
@@ -43,7 +73,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 +108,51 @@ 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
+ * @throws UserTokenException
+ */
+ private function validateUserType(int $userType): void
+ {
+ if ($userType !== CustomUserContext::USER_TYPE_ADMIN
+ && $userType !== CustomUserContext::USER_TYPE_CUSTOMER
+ && $userType !== CustomUserContext::USER_TYPE_INTEGRATION
+ ) {
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();
+ $userId = null;
+
if ($userType === CustomUserContext::USER_TYPE_ADMIN) {
$userId = $tokenModel->getAdminId();
+ } elseif ($userType === CustomUserContext::USER_TYPE_INTEGRATION) {
+ $integration = $this->integrationService->findByConsumerId($tokenModel->getConsumerId());
+ if ($this->bearerTokenValidator->isIntegrationAllowedAsBearerToken($integration)) {
+ $userId = $integration->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 (int)$userId;
}
}
diff --git a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php
index 3e19fed38aead..b22f146d6c64e 100644
--- a/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php
+++ b/app/code/Magento/Integration/Model/UserToken/ExpirationValidator.php
@@ -8,11 +8,15 @@
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;
use Magento\Framework\Stdlib\DateTime\DateTime as DtUtil;
+/**
+ * Validates if a token is expired
+ */
class ExpirationValidator implements UserTokenValidatorInterface
{
/**
@@ -33,8 +37,30 @@ public function __construct(DtUtil $datetimeUtil)
*/
public function validate(UserToken $token): void
{
- if ($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;
+ }
}
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..a18cc64d891a0
--- /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 isIntegrationAllowedAsBearerToken(Integration $integration): bool
+ {
+ return $this->authorizationConfig->isIntegrationAsBearerEnabled();
+ }
+}
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/Config/AuthorizationConfigTest.php b/app/code/Magento/Integration/Test/Unit/Model/Config/AuthorizationConfigTest.php
new file mode 100644
index 0000000000000..96832b3c5d1a5
--- /dev/null
+++ b/app/code/Magento/Integration/Test/Unit/Model/Config/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('oauth/consumer/enable_integration_as_bearer')
+ ->willReturn(true);
+
+ self::assertTrue($this->config->isIntegrationAsBearerEnabled());
+ }
+
+ public function testDisabled()
+ {
+ $this->scopeConfig->method('isSetFlag')
+ ->with('oauth/consumer/enable_integration_as_bearer')
+ ->willReturn(false);
+
+ self::assertFalse($this->config->isIntegrationAsBearerEnabled());
+ }
+}
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/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/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/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 @@
+
+
+
-
-
-
diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php
index b5588a9a49a2f..5197a56b3a7f1 100644
--- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php
+++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php
@@ -4,18 +4,25 @@
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Webapi\Controller\Rest;
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;
use Magento\Framework\Webapi\Rest\Request as RestRequest;
+use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue;
use Magento\Webapi\Controller\Rest\Router\Route;
/**
* This class is responsible for retrieving resolved input data
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class InputParamsResolver
{
@@ -54,6 +61,11 @@ class InputParamsResolver
*/
private $methodsMap;
+ /**
+ * @var InputArraySizeLimitValue
+ */
+ private $inputArraySizeLimitValue;
+
/**
* Initialize dependencies
*
@@ -63,6 +75,7 @@ class InputParamsResolver
* @param Router $router
* @param RequestValidator $requestValidator
* @param MethodsMap|null $methodsMap
+ * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue
*/
public function __construct(
RestRequest $request,
@@ -70,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;
@@ -79,29 +93,34 @@ 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|LocalizedException
*/
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(),
+ );
}
/**
* Get API input data
*
* @return array
+ * @throws InputException|Exception
*/
public function getInputData()
{
@@ -131,6 +150,7 @@ public function getInputData()
* Retrieve current route.
*
* @return Route
+ * @throws Exception
*/
public function getRoute()
{
@@ -174,9 +194,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/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..2c54b21624811 100644
--- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php
+++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php
@@ -4,18 +4,23 @@
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
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\Request as WebapiRequest;
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;
@@ -30,45 +35,45 @@
*/
class Handler
{
- const RESULT_NODE_NAME = 'result';
+ public const RESULT_NODE_NAME = 'result';
/**
- * @var \Magento\Framework\Webapi\Request
+ * @var WebapiRequest
*/
protected $_request;
/**
- * @var \Magento\Framework\ObjectManagerInterface
+ * @var ObjectManagerInterface
*/
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;
@@ -77,11 +82,16 @@ class Handler
*/
private $paramsOverrider;
+ /**
+ * @var InputArraySizeLimitValue
+ */
+ private $inputArraySizeLimitValue;
+
/**
* Initialize dependencies.
*
- * @param SoapRequest $request
- * @param \Magento\Framework\ObjectManagerInterface $objectManager
+ * @param WebapiRequest $request
+ * @param ObjectManagerInterface $objectManager
* @param SoapConfig $apiConfig
* @param Authorization $authorization
* @param SimpleDataObjectConverter $dataObjectConverter
@@ -89,17 +99,20 @@ class Handler
* @param DataObjectProcessor $dataObjectProcessor
* @param MethodsMap $methodsMapProcessor
* @param ParamsOverrider|null $paramsOverrider
+ * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- SoapRequest $request,
- \Magento\Framework\ObjectManagerInterface $objectManager,
+ WebapiRequest $request,
+ 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;
@@ -110,6 +123,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);
}
/**
@@ -144,10 +159,24 @@ 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 object $service
+ * @param string $serviceMethod
+ * @param array $inputData
+ * @return false|mixed
+ */
+ private function runServiceMethod($service, $serviceMethod, $inputData)
+ {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ return call_user_func_array([$service, $serviceMethod], $inputData);
+ }
+
/**
* Convert arguments received from SOAP server to arguments to pass to a service.
*
@@ -156,7 +185,6 @@ public function __call($operation, $arguments)
* @param array $arguments
* @return array
* @throws WebapiException
- * @throws \Magento\Framework\Exception\InputException
*/
private function prepareOperationInput(string $serviceClass, array $methodMetadata, array $arguments): array
{
@@ -164,6 +192,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,
@@ -179,8 +208,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)
{
@@ -198,7 +228,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)
{
@@ -225,7 +255,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/Webapi/Model/Authorization/SoapUserContext.php b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php
index 78de066ac31ee..d225f0d96546e 100644
--- a/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php
+++ b/app/code/Magento/Webapi/Model/Authorization/SoapUserContext.php
@@ -12,9 +12,7 @@
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.
@@ -49,7 +47,12 @@ class SoapUserContext implements UserContextInterface
/**
* @var IntegrationServiceInterface
*/
- private $integrationService;
+ private IntegrationServiceInterface $integrationService;
+
+ /**
+ * @var BearerTokenValidator
+ */
+ private BearerTokenValidator $bearerTokenValidator;
/**
* Initialize dependencies.
@@ -57,18 +60,19 @@ class SoapUserContext implements UserContextInterface
* @param Request $request
* @param TokenFactory $tokenFactory
* @param IntegrationServiceInterface $integrationService
- * @param DateTime|null $dateTime
- * @param Date|null $date
- * @param OauthHelper|null $oauthHelper
+ * @param BearerTokenValidator|null $bearerTokenValidator
*/
public function __construct(
Request $request,
TokenFactory $tokenFactory,
- IntegrationServiceInterface $integrationService
+ IntegrationServiceInterface $integrationService,
+ ?BearerTokenValidator $bearerTokenValidator = null
) {
$this->request = $request;
$this->tokenFactory = $tokenFactory;
$this->integrationService = $integrationService;
+ $this->bearerTokenValidator = $bearerTokenValidator ?? ObjectManager::getInstance()
+ ->get(BearerTokenValidator::class);
}
/**
@@ -114,6 +118,7 @@ private function processRequest() //phpcs:ignore CopyPaste
$this->isRequestProcessed = true;
return;
}
+
$bearerToken = $headerPieces[1];
/** @var Token $token */
@@ -123,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->isIntegrationAllowedAsBearerToken($integration)) {
+ $this->userId = $integration->getId();
+ $this->userType = UserContextInterface::USER_TYPE_INTEGRATION;
+ }
}
$this->isRequestProcessed = true;
}
diff --git a/app/code/Magento/Webapi/Model/Config/Converter.php b/app/code/Magento/Webapi/Model/Config/Converter.php
index b05b1a25b3dc4..9ee9d745c9a45 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;
/**
@@ -13,22 +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';
+ 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';
/**#@-*/
/**
@@ -96,6 +100,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,
@@ -105,6 +111,7 @@ public function convert($source)
],
self::KEY_ACL_RESOURCES => $resourceReferences,
self::KEY_DATA_PARAMETERS => $data,
+ self::KEY_INPUT_ARRAY_SIZE_LIMIT => $arraySizeLimit,
];
$serviceSecure = false;
@@ -114,6 +121,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;
@@ -169,4 +179,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..59a7f45eb8abe 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;
@@ -17,25 +20,28 @@ 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';
+ 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';
/*#@-*/
- /*#@-*/
+ /**
+ * @var ModelConfigInterface
+ */
protected $_config;
/**
@@ -79,7 +85,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 +127,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 +151,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..fe44f7814b161 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;
@@ -18,35 +21,39 @@ 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 SERVICES_CONFIG_CACHE_ID = 'services-services-config';
+ public const KEY_INPUT_ARRAY_SIZE_LIMIT = 'input-array-size-limit';
- const ROUTES_CONFIG_CACHE_ID = 'routes-services-config';
+ public const SERVICES_CONFIG_CACHE_ID = 'services-services-config';
- const REFLECTED_TYPES_CACHE_ID = 'soap-reflected-types';
+ public const ROUTES_CONFIG_CACHE_ID = 'routes-services-config';
- /**#@-*/
+ public const REFLECTED_TYPES_CACHE_ID = 'soap-reflected-types';
/**#@-*/
+
+ /**
+ * @var array
+ */
protected $services;
/**
@@ -123,7 +130,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];
@@ -134,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]]
@@ -311,9 +320,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/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/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/Webapi/etc/webapi_soap/di.xml b/app/code/Magento/Webapi/etc/webapi_soap/di.xml
index f6fef5498aa76..7433e73794283 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/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php
index 064bd99b9b6bf..8601e5011bda7 100644
--- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php
+++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php
@@ -8,12 +8,19 @@
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;
use Magento\Webapi\Controller\Rest\Router;
+use Magento\Webapi\Controller\Rest\Router\Route;
/**
* This class is responsible for retrieving resolved input data
@@ -41,7 +48,7 @@ class InputParamsResolver
*/
private $requestValidator;
/**
- * @var \Magento\Webapi\Controller\Rest\InputParamsResolver
+ * @var WebapiInputParamsResolver
*/
private $inputParamsResolver;
/**
@@ -49,16 +56,22 @@ class InputParamsResolver
*/
private $isBulk;
+ /**
+ * @var InputArraySizeLimitValue|null
+ */
+ private $inputArraySizeLimitValue;
+
/**
* 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
*/
public function __construct(
RestRequest $request,
@@ -67,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;
@@ -76,6 +90,8 @@ public function __construct(
$this->requestValidator = $requestValidator;
$this->inputParamsResolver = $inputParamsResolver;
$this->isBulk = $isBulk;
+ $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance()
+ ->get(InputArraySizeLimitValue::class);
}
/**
@@ -85,20 +101,31 @@ 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|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);
+ $webapiResolvedParams[$key] = $this->resolveBulkItemParams(
+ $singleEntityParams,
+ $routeServiceClass,
+ $routeServiceMethod
+ );
}
+
return $webapiResolvedParams;
}
@@ -115,7 +142,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);
}
@@ -125,7 +152,8 @@ public function getInputData()
/**
* Returns route.
*
- * @return \Magento\Webapi\Controller\Rest\Router\Route
+ * @return Route
+ * @throws Exception
*/
public function getRoute()
{
@@ -142,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 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 \Magento\Framework\Exception\InputException if no value is provided for required parameters
- * @throws \Magento\Framework\Webapi\Exception
+ * @throws Exception|LocalizedException
*/
- private function resolveBulkItemParams($inputData)
+ private function resolveBulkItemParams(array $inputData, string $serviceClass, string $serviceMethod): array
{
- $route = $this->getRoute();
- $serviceMethodName = $route->getServiceMethod();
- $serviceClassName = $route->getServiceClass();
- $inputParams = $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData);
-
- return $inputParams;
+ return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData);
}
}
diff --git a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php
index 2c85796a3ab19..74a4adb4b1819 100644
--- a/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php
+++ b/app/code/Magento/WebapiAsync/Model/ServiceConfig/Converter.php
@@ -16,13 +16,17 @@ 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';
+ 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';
/**#@-*/
+ /**
+ * @var array
+ */
private $allowedRouteMethods = [
\Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
\Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
@@ -32,7 +36,8 @@ class Converter implements \Magento\Framework\Config\ConverterInterface
];
/**
- * {@inheritdoc}
+ * @inheritDoc
+ *
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
@@ -81,7 +86,10 @@ private function mergeSynchronousInvocationMethodsData(
}
/**
+ * Checks if xml node can be converted
+ *
* @param \DOMElement $node
+ *
* @return bool
*/
private function canConvertXmlNode(\DOMElement $node)
@@ -120,7 +128,10 @@ private function initServiceMethodsKey(array &$result, $serviceClass, $serviceMe
}
/**
+ * Returns service class
+ *
* @param \DOMElement $service
+ *
* @return null|string
*/
private function getServiceClass(\DOMElement $service)
@@ -131,7 +142,10 @@ private function getServiceClass(\DOMElement $service)
}
/**
+ * Returns service method
+ *
* @param \DOMElement $service
+ *
* @return null|string
*/
private function getServiceMethod(\DOMElement $service)
@@ -142,7 +156,10 @@ private function getServiceMethod(\DOMElement $service)
}
/**
+ * Checks if synchronous method invocation only
+ *
* @param \DOMElement $serviceNode
+ *
* @return bool
*/
private function isSynchronousMethodInvocationOnly(\DOMElement $serviceNode)
@@ -153,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)
@@ -171,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)
@@ -183,18 +205,23 @@ 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;
}
/**
+ * Returns route url
+ *
* @param \DOMElement $route
+ *
* @return null|string
*/
private function getRouteUrl($route)
@@ -204,7 +231,10 @@ private function getRouteUrl($route)
}
/**
+ * Returns route alias
+ *
* @param \DOMElement $route
+ *
* @return null|string
*/
private function getRouteAlias($route)
@@ -214,7 +244,10 @@ private function getRouteAlias($route)
}
/**
+ * Returns route method
+ *
* @param \DOMElement $route
+ *
* @return null|string
*/
private function getRouteMethod($route)
@@ -225,11 +258,36 @@ private function getRouteMethod($route)
}
/**
+ * Validates method of route
+ *
* @param string $method
+ *
* @return bool
*/
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..739f27cbd090c
--- /dev/null
+++ b/app/code/Magento/WebapiAsync/Plugin/Rest/Config.php
@@ -0,0 +1,86 @@
+serviceConfig = $serviceConfig;
+ }
+
+ /**
+ * Overrides the rules for an asynchronous request
+ *
+ * @param RestConfig $restConfig
+ * @param array $routes
+ * @param Request $request
+ * @return Route[]
+ * @throws InputException
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ 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/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/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/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/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/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php
index 7ccab097d7778..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,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/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php b/dev/tests/api-functional/testsuite/Magento/Integration/Model/IntegrationTest.php
index 489e7d2517527..a5d78c9054936 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
+ * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 0
+ */
+ 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
+ *
+ * @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);
+ }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php
index 91be257429314..4a05a83351685 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();
}
+ /**
+ * @magentoConfigFixture default_store oauth/consumer/enable_integration_as_bearer 0
+ */
+ 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);
+ }
+
public function testMultiServiceWsdl()
{
$this->_soapUrl = "{$this->_baseUrl}/soap/{$this->_storeCode}"
@@ -41,12 +76,7 @@ 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);
}
public function testSingleServiceWsdl()
@@ -56,12 +86,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 +1008,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);
+ }
}
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/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);
diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php
index 96999187bd6cb..670c74dd197bc 100644
--- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php
+++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php
@@ -18,143 +18,153 @@ 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
+ */
+ 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
+ * 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
*/
- 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';
/**
- * @deprecated
- *
* 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/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php
index 6aef1a61922c8..ff1c538402133 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)
@@ -167,6 +168,8 @@ function () use ($objectManager) {
->disableOriginalConstructor()
->getMock();
+ $inputArraySizeLimitValue = $this->createMock(InputArraySizeLimitValue::class);
+
$this->defaultPageSizeSetter = self::getMockBuilder(DefaultPageSizeSetter::class)
->disableOriginalConstructor()
->getMock();
@@ -180,7 +183,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,
+ $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..91ac018d40cbe 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 91019006af301..65a3df9bd7696 100644
--- a/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php
+++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator.php
@@ -9,8 +9,11 @@
namespace Magento\Framework\Webapi\Validator;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\InvalidArgumentException;
use Magento\Framework\Webapi\Validator\IOLimit\IOLimitConfigProvider;
+use Magento\Framework\Exception\RuntimeException;
+use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue;
/**
* Validates service input
@@ -20,26 +23,38 @@ class EntityArrayValidator implements ServiceInputValidatorInterface
/**
* @var int
*/
- private $complexArrayItemLimit;
+ private int $complexArrayItemLimit;
/**
- * @var IOLimitConfigProvider|null
+ * @var IOLimitConfigProvider
*/
private $configProvider;
+ /**
+ * @var InputArraySizeLimitValue
+ */
+ private $inputArraySizeLimitValue;
+
/**
* @param int $complexArrayItemLimit
* @param IOLimitConfigProvider|null $configProvider
+ * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue
*/
- public function __construct(int $complexArrayItemLimit, ?IOLimitConfigProvider $configProvider = null)
- {
+ public function __construct(
+ int $complexArrayItemLimit,
+ ?IOLimitConfigProvider $configProvider = null,
+ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null
+ ) {
$this->complexArrayItemLimit = $complexArrayItemLimit;
- $this->configProvider = $configProvider ?? ObjectManager::getInstance()
- ->get(IOLimitConfigProvider::class);
+ $this->configProvider = $configProvider ?? ObjectManager::getInstance()->get(IOLimitConfigProvider::class);
+ $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance()
+ ->get(InputArraySizeLimitValue::class);
}
/**
* @inheritDoc
+ *
+ * @throws FileSystemException|RuntimeException
*/
public function validateComplexArrayType(string $className, array $items): void
{
@@ -47,13 +62,14 @@ public function validateComplexArrayType(string $className, array $items): void
return;
}
- $max = $this->configProvider->getComplexArrayItemLimit() ?? $this->complexArrayItemLimit;
+ $maxLimit = $this->inputArraySizeLimitValue->get()
+ ?? ($this->configProvider->getComplexArrayItemLimit() ?? $this->complexArrayItemLimit);
- if (count($items) > $max) {
+ if (count($items) > $maxLimit) {
throw new InvalidArgumentException(
__(
'Maximum items of type "%type" is %max',
- ['type' => $className, 'max' => $max]
+ ['type' => $className, 'max' => $maxLimit]
)
);
}
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..ca75da4a93eed
--- /dev/null
+++ b/lib/internal/Magento/Framework/Webapi/Validator/EntityArrayValidator/InputArraySizeLimitValue.php
@@ -0,0 +1,95 @@
+request = $request;
+ $this->deploymentConfig = $deploymentConfig;
+ }
+
+ /**
+ * Set value of input array size limit
+ *
+ * @param int|null $value
+ */
+ public function set(?int $value): void
+ {
+ $this->value = $value;
+ }
+
+ /**
+ * Get value of input array size limit
+ *
+ * @return int|null
+ * @throws FileSystemException
+ * @throws RuntimeException
+ */
+ public function get(): ?int
+ {
+ 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;
+ }
+}