From 7756bb98c9bd299692cf266548cd608958a46273 Mon Sep 17 00:00:00 2001 From: Andrii Poltoratskyi Date: Tue, 16 Jan 2024 13:51:30 -0800 Subject: [PATCH 01/82] MCLOUD-11623: Added ACSD-55623 patch --- patches.json | 3 +++ ...rts_saved_to_var_directory__2.4.5-p1.patch | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 patches/MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch diff --git a/patches.json b/patches.json index ea81ffb..e421f82 100644 --- a/patches.json +++ b/patches.json @@ -370,6 +370,9 @@ "magento/magento2-b2b-base": { "Layered navigation filter is present only when product is present on the listing page with enabled Shared catalog": { ">=1.1.5 <1.3.1": "MCLOUD-6923__layered_navigation_filter_is_present_only_when_product_is_present_on_the_listing_page_with_enabled_shared_catalog__2.3.5.patch" + }, + "Fixes the issue where the file generated after Requisition List export is not removed from the var/ directory": { + ">=1.3.1 <1.3.6": "MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch" } }, "magento/magento2-ee-base": { diff --git a/patches/MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch b/patches/MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch new file mode 100644 index 0000000..bcd2991 --- /dev/null +++ b/patches/MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch @@ -0,0 +1,22 @@ +diff --git a/vendor/magento/module-requisition-list/Controller/Requisition/Export.php b/vendor/magento/module-requisition-list/Controller/Requisition/Export.php +index e8332a7f1091..7eee2f51b7f4 100644 +--- a/vendor/magento/module-requisition-list/Controller/Requisition/Export.php ++++ b/vendor/magento/module-requisition-list/Controller/Requisition/Export.php +@@ -101,9 +101,15 @@ public function execute() + + $fileName = "{$requisitionList->getName()}.{$writer->getFileExtension()}"; + ++ $content = [ ++ 'type' => "string", ++ 'value' => $this->requisitionListExport->export(), ++ 'rm' => true, ++ ]; ++ + return $this->fileFactory->create( + $fileName, +- $this->requisitionListExport->export(), ++ $content, + DirectoryList::VAR_DIR, + $writer->getContentType() + ); + From ca1c0948e9587002613181a9b564b3006bb25c13 Mon Sep 17 00:00:00 2001 From: Shambhu Kumar Date: Mon, 18 Mar 2024 21:39:07 +0530 Subject: [PATCH 02/82] Updated testcase --- .../Acceptance/Acceptance82Cest.php | 57 +++++++++++++++++++ .../Functional/Acceptance/AcceptanceCest.php | 6 +- .../Acceptance/PatchApplierCest.php | 4 +- 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 src/Test/Functional/Acceptance/Acceptance82Cest.php diff --git a/src/Test/Functional/Acceptance/Acceptance82Cest.php b/src/Test/Functional/Acceptance/Acceptance82Cest.php new file mode 100644 index 0000000..79cd321 --- /dev/null +++ b/src/Test/Functional/Acceptance/Acceptance82Cest.php @@ -0,0 +1,57 @@ +prepareTemplate($I, $data['templateVersion'], $data['magentoVersion'] ?? null); + $I->copyFileToWorkDir('files/patches/.apply_quality_patches.env.yaml', '.magento.env.yaml'); + $I->generateDockerCompose(sprintf( + '--mode=production --env-vars="%s"', + $this->convertEnvFromArrayToJson(['MAGENTO_CLOUD_PROJECT' => 'travis-testing']) + )); + $I->assertTrue($I->runDockerComposeCommand('run build cloud-build')); + $I->assertTrue($I->startEnvironment()); + $I->assertTrue($I->runDockerComposeCommand('run deploy cloud-deploy')); + $I->assertTrue($I->runDockerComposeCommand('run deploy cloud-post-deploy')); + $I->amOnPage('/'); + $I->see('Home page'); + $I->see('CMS homepage content goes here.'); + } + + /** + * @return array + */ + protected function patchesDataProvider(): array + { + return [ + ['templateVersion' => '2.4.6', 'magentoVersion' => '2.4.6'], + ['templateVersion' => '2.4.6', 'magentoVersion' => '2.4.6-p1'], + ['templateVersion' => '2.4.7-beta', 'magentoVersion' => null], + ]; + } +} diff --git a/src/Test/Functional/Acceptance/AcceptanceCest.php b/src/Test/Functional/Acceptance/AcceptanceCest.php index 79cd321..cedf47e 100644 --- a/src/Test/Functional/Acceptance/AcceptanceCest.php +++ b/src/Test/Functional/Acceptance/AcceptanceCest.php @@ -8,7 +8,7 @@ namespace Magento\CloudPatches\Test\Functional\Acceptance; /** - * @group php82 + * @group php83 */ class AcceptanceCest extends AbstractCest { @@ -49,9 +49,7 @@ public function testPatches(\CliTester $I, \Codeception\Example $data): void protected function patchesDataProvider(): array { return [ - ['templateVersion' => '2.4.6', 'magentoVersion' => '2.4.6'], - ['templateVersion' => '2.4.6', 'magentoVersion' => '2.4.6-p1'], - ['templateVersion' => '2.4.7-beta', 'magentoVersion' => null], + ['templateVersion' => '2.4.7-beta-test', 'magentoVersion' => '2.4.7-beta401'], ]; } } diff --git a/src/Test/Functional/Acceptance/PatchApplierCest.php b/src/Test/Functional/Acceptance/PatchApplierCest.php index 0b5b6e8..abf4508 100644 --- a/src/Test/Functional/Acceptance/PatchApplierCest.php +++ b/src/Test/Functional/Acceptance/PatchApplierCest.php @@ -10,7 +10,7 @@ use Magento\CloudDocker\Test\Functional\Codeception\Docker; /** - * @group php82 + * @group php83 */ class PatchApplierCest extends AbstractCest { @@ -21,7 +21,7 @@ public function _before(\CliTester $I): void { parent::_before($I); - $this->prepareTemplate($I, '2.4.6'); + $this->prepareTemplate($I, '2.4.7-beta-test'); $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); } From 9b3ac3ca9731de172d4c5c661f0d5a3c4e4627d7 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Tue, 19 Mar 2024 10:05:41 -0500 Subject: [PATCH 03/82] php 82 test file fix --- .../Acceptance/Acceptance82Cest.php | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/src/Test/Functional/Acceptance/Acceptance82Cest.php b/src/Test/Functional/Acceptance/Acceptance82Cest.php index 79cd321..d9ea8d6 100644 --- a/src/Test/Functional/Acceptance/Acceptance82Cest.php +++ b/src/Test/Functional/Acceptance/Acceptance82Cest.php @@ -10,39 +10,8 @@ /** * @group php82 */ -class AcceptanceCest extends AbstractCest +class Acceptance82Cest extends AcceptanceCest { - /** - * @param \CliTester $I - */ - public function _before(\CliTester $I): void - { - parent::_before($I); - } - - /** - * @param \CliTester $I - * @param \Codeception\Example $data - * @throws \Robo\Exception\TaskException - * @dataProvider patchesDataProvider - */ - public function testPatches(\CliTester $I, \Codeception\Example $data): void - { - $this->prepareTemplate($I, $data['templateVersion'], $data['magentoVersion'] ?? null); - $I->copyFileToWorkDir('files/patches/.apply_quality_patches.env.yaml', '.magento.env.yaml'); - $I->generateDockerCompose(sprintf( - '--mode=production --env-vars="%s"', - $this->convertEnvFromArrayToJson(['MAGENTO_CLOUD_PROJECT' => 'travis-testing']) - )); - $I->assertTrue($I->runDockerComposeCommand('run build cloud-build')); - $I->assertTrue($I->startEnvironment()); - $I->assertTrue($I->runDockerComposeCommand('run deploy cloud-deploy')); - $I->assertTrue($I->runDockerComposeCommand('run deploy cloud-post-deploy')); - $I->amOnPage('/'); - $I->see('Home page'); - $I->see('CMS homepage content goes here.'); - } - /** * @return array */ From cb971584ea27012600a33628f820b681061ec4b8 Mon Sep 17 00:00:00 2001 From: Shambhu Kumar Date: Fri, 10 May 2024 20:48:22 +0530 Subject: [PATCH 04/82] Removed old packages --- composer.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index af82866..acd62ee 100644 --- a/composer.json +++ b/composer.json @@ -11,17 +11,17 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0", "ext-json": "*", - "composer/composer": "^1.4 || ^2.0", + "composer/composer": "^1.9 || ^2.0", "composer/semver": "@stable", - "symfony/config": "^3.3||^4.4||^5.0||^6.0", - "symfony/console": "^2.6||^4.0||^5.0||^6.0", - "symfony/dependency-injection": "^3.3||^4.3||^5.0||^6.0", - "symfony/process": "^2.1||^4.1||^5.0||^6.0", - "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", - "symfony/yaml": "^3.3||^4.0||^5.0||^6.0", - "monolog/monolog": "^1.25||^2.3||^2.7", + "symfony/config": "^4.4||^5.0||^6.0", + "symfony/console": "^4.4||^5.0||^6.0", + "symfony/dependency-injection": "^4.4||^5.0||^6.0", + "symfony/process": "^4.4||^5.0||^6.0", + "symfony/proxy-manager-bridge": "^4.4||^5.0||^6.0", + "symfony/yaml": "^4.4||^5.0||^6.0", + "monolog/monolog": "^2.3||^2.7", "magento/quality-patches": "^1.1.0" }, "require-dev": { @@ -30,7 +30,7 @@ "codeception/module-db": "^1.0", "codeception/module-phpbrowser": "^1.0", "codeception/module-rest": "^1.2", - "consolidation/robo": "^1.2 || ^2.0", + "consolidation/robo": "^2.0", "phpmd/phpmd": "@stable", "phpunit/phpunit": "^8.5 || ^9.5", "squizlabs/php_codesniffer": "^3.0" From c7df92df9d05cbfe474656c9730585081db8c1dd Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Mon, 13 May 2024 15:54:57 -0500 Subject: [PATCH 05/82] update template version --- composer.json | 2 +- src/Test/Functional/Acceptance/Acceptance82Cest.php | 2 +- src/Test/Functional/Acceptance/AcceptanceCest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index af82866..967556d 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "magento/quality-patches": "^1.1.0" }, "require-dev": { - "codeception/codeception": "^4.1", + "codeception/codeception": "^4.1 || ^5.1", "codeception/module-asserts": "^1.2", "codeception/module-db": "^1.0", "codeception/module-phpbrowser": "^1.0", diff --git a/src/Test/Functional/Acceptance/Acceptance82Cest.php b/src/Test/Functional/Acceptance/Acceptance82Cest.php index d9ea8d6..4433514 100644 --- a/src/Test/Functional/Acceptance/Acceptance82Cest.php +++ b/src/Test/Functional/Acceptance/Acceptance82Cest.php @@ -20,7 +20,7 @@ protected function patchesDataProvider(): array return [ ['templateVersion' => '2.4.6', 'magentoVersion' => '2.4.6'], ['templateVersion' => '2.4.6', 'magentoVersion' => '2.4.6-p1'], - ['templateVersion' => '2.4.7-beta', 'magentoVersion' => null], + ['templateVersion' => '2.4.7', 'magentoVersion' => null], ]; } } diff --git a/src/Test/Functional/Acceptance/AcceptanceCest.php b/src/Test/Functional/Acceptance/AcceptanceCest.php index cedf47e..fef4300 100644 --- a/src/Test/Functional/Acceptance/AcceptanceCest.php +++ b/src/Test/Functional/Acceptance/AcceptanceCest.php @@ -49,7 +49,7 @@ public function testPatches(\CliTester $I, \Codeception\Example $data): void protected function patchesDataProvider(): array { return [ - ['templateVersion' => '2.4.7-beta-test', 'magentoVersion' => '2.4.7-beta401'], + ['templateVersion' => '2.4.7', 'magentoVersion' => '2.4.7'], ]; } } From fd9a6b65433f616ede05b8e58225165bf7589887 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Mon, 13 May 2024 17:01:31 -0500 Subject: [PATCH 06/82] update template --- src/Test/Functional/Acceptance/PatchApplierCest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Test/Functional/Acceptance/PatchApplierCest.php b/src/Test/Functional/Acceptance/PatchApplierCest.php index abf4508..05a0990 100644 --- a/src/Test/Functional/Acceptance/PatchApplierCest.php +++ b/src/Test/Functional/Acceptance/PatchApplierCest.php @@ -21,7 +21,7 @@ public function _before(\CliTester $I): void { parent::_before($I); - $this->prepareTemplate($I, '2.4.7-beta-test'); + $this->prepareTemplate($I, '2.4.7'); $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); } From e76faaa30c6fcbeb2089a6d719cb08d0bec89fb2 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Mon, 13 May 2024 17:36:20 -0500 Subject: [PATCH 07/82] update compatible versions --- composer.json | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 967556d..74de6da 100644 --- a/composer.json +++ b/composer.json @@ -6,8 +6,8 @@ "license": "OSL-3.0", "repositories": { "repo.magento.com": { - "type": "composer", - "url": "https://repo.magento.com/" + "type": "composer", + "url": "https://repo.magento.com/" } }, "require": { @@ -18,19 +18,18 @@ "symfony/config": "^3.3||^4.4||^5.0||^6.0", "symfony/console": "^2.6||^4.0||^5.0||^6.0", "symfony/dependency-injection": "^3.3||^4.3||^5.0||^6.0", - "symfony/process": "^2.1||^4.1||^5.0||^6.0", + "symfony/process": "^2.1 || ^4.1 || ^5.1 || ^5.4 || ^6.4", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", "symfony/yaml": "^3.3||^4.0||^5.0||^6.0", - "monolog/monolog": "^1.25||^2.3||^2.7", - "magento/quality-patches": "^1.1.0" + "monolog/monolog": "^1.25||^2.3||^2.7" }, "require-dev": { "codeception/codeception": "^4.1 || ^5.1", - "codeception/module-asserts": "^1.2", - "codeception/module-db": "^1.0", - "codeception/module-phpbrowser": "^1.0", - "codeception/module-rest": "^1.2", - "consolidation/robo": "^1.2 || ^2.0", + "codeception/module-asserts": "^1.2 || ^3.0", + "codeception/module-db": "^1.0 || ^3.0", + "codeception/module-phpbrowser": "^1.0 || ^3.0", + "codeception/module-rest": "^1.2 || ^3.0", + "consolidation/robo": "^1.2 || ^3.0", "phpmd/phpmd": "@stable", "phpunit/phpunit": "^8.5 || ^9.5", "squizlabs/php_codesniffer": "^3.0" From 37377871916fd9783a5af2cd89187ee6be3d418a Mon Sep 17 00:00:00 2001 From: Tom Reece Date: Wed, 15 May 2024 14:34:39 -0500 Subject: [PATCH 08/82] Use abstract parent cest to fix tests --- .../Acceptance/Acceptance83Cest.php | 24 +++++++++++++++++++ .../Functional/Acceptance/AcceptanceCest.php | 15 +----------- 2 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 src/Test/Functional/Acceptance/Acceptance83Cest.php diff --git a/src/Test/Functional/Acceptance/Acceptance83Cest.php b/src/Test/Functional/Acceptance/Acceptance83Cest.php new file mode 100644 index 0000000..412ad87 --- /dev/null +++ b/src/Test/Functional/Acceptance/Acceptance83Cest.php @@ -0,0 +1,24 @@ + '2.4.7', 'magentoVersion' => '2.4.7'], + ]; + } +} diff --git a/src/Test/Functional/Acceptance/AcceptanceCest.php b/src/Test/Functional/Acceptance/AcceptanceCest.php index fef4300..e22af9c 100644 --- a/src/Test/Functional/Acceptance/AcceptanceCest.php +++ b/src/Test/Functional/Acceptance/AcceptanceCest.php @@ -7,10 +7,7 @@ namespace Magento\CloudPatches\Test\Functional\Acceptance; -/** - * @group php83 - */ -class AcceptanceCest extends AbstractCest +abstract class AcceptanceCest extends AbstractCest { /** * @param \CliTester $I @@ -42,14 +39,4 @@ public function testPatches(\CliTester $I, \Codeception\Example $data): void $I->see('Home page'); $I->see('CMS homepage content goes here.'); } - - /** - * @return array - */ - protected function patchesDataProvider(): array - { - return [ - ['templateVersion' => '2.4.7', 'magentoVersion' => '2.4.7'], - ]; - } } From 342282193f58cfb06b10404bb1f5703216be7b3e Mon Sep 17 00:00:00 2001 From: Tom Reece Date: Wed, 15 May 2024 14:41:21 -0500 Subject: [PATCH 09/82] Add abstract patchesDataProvider signature --- src/Test/Functional/Acceptance/AcceptanceCest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Test/Functional/Acceptance/AcceptanceCest.php b/src/Test/Functional/Acceptance/AcceptanceCest.php index e22af9c..c960a58 100644 --- a/src/Test/Functional/Acceptance/AcceptanceCest.php +++ b/src/Test/Functional/Acceptance/AcceptanceCest.php @@ -39,4 +39,9 @@ public function testPatches(\CliTester $I, \Codeception\Example $data): void $I->see('Home page'); $I->see('CMS homepage content goes here.'); } + + /** + * @return array + */ + abstract protected function patchesDataProvider(): array; } From c7ab9d96d0ef27731ce8058e53c7cea5d10e07b1 Mon Sep 17 00:00:00 2001 From: Andrii Dimov Date: Mon, 20 May 2024 08:48:50 -0500 Subject: [PATCH 10/82] ACPT-1876: Create Cloud quality patch for Application Server known functional issue --- patches.json | 5 ++ ...ld_use_factory_for_collection__2.4.7.patch | 75 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch diff --git a/patches.json b/patches.json index ea81ffb..c66cf87 100644 --- a/patches.json +++ b/patches.json @@ -410,5 +410,10 @@ "Fix regexp cache tag validation": { ">=103.0.6 <103.0.7": "MCLOUD-10226__fix_regexp_cache_tag_validation__2.4.6.patch" } + }, + "magento/module-catalog-graph-ql": { + "AttributeReader should use Factory for Collection": { + ">=100.4.7 <100.4.7": "ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch" + } } } diff --git a/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch b/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch new file mode 100644 index 0000000..314ebcd --- /dev/null +++ b/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch @@ -0,0 +1,75 @@ +From c964bc3248811dc63df6205a1246d383ad4c6e4a Mon Sep 17 00:00:00 2001 +From: Jacob Brown +Date: Wed, 3 Apr 2024 14:07:21 -0500 +Subject: [PATCH] ACPT-1854: AttributeReader should use Factory for Collection + +--- + .../Model/Config/AttributeReader.php | 18 +++++++++++------- + 1 file changed, 11 insertions(+), 7 deletions(-) + +diff --git a/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php b/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php +index ecd83bf61ef0..05acb97e4bd7 100644 +--- a/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php ++++ b/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php +@@ -9,8 +9,10 @@ + use Magento\Catalog\Model\Product; + use Magento\Catalog\Model\ResourceModel\Eav\Attribute; + use Magento\CatalogGraphQl\Model\Resolver\Products\Attributes\Collection; ++use Magento\CatalogGraphQl\Model\Resolver\Products\Attributes\CollectionFactory; + use Magento\EavGraphQl\Model\Resolver\Query\Type; + use Magento\Framework\App\Config\ScopeConfigInterface; ++use Magento\Framework\App\ObjectManager; + use Magento\Framework\Config\ReaderInterface; + use Magento\Framework\GraphQl\Exception\GraphQlInputException; + use Magento\Framework\GraphQl\Schema\Type\Entity\MapperInterface; +@@ -36,9 +38,9 @@ class AttributeReader implements ReaderInterface + private Type $typeLocator; + + /** +- * @var Collection ++ * @var CollectionFactory + */ +- private Collection $collection; ++ private readonly CollectionFactory $collectionFactory; + + /** + * @var ScopeConfigInterface +@@ -48,18 +50,21 @@ class AttributeReader implements ReaderInterface + /** + * @param MapperInterface $mapper + * @param Type $typeLocator +- * @param Collection $collection ++ * @param Collection $collection @deprecated @see $collectionFactory + * @param ScopeConfigInterface $config ++ * @param CollectionFactory|null $collectionFactory ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __construct( + MapperInterface $mapper, + Type $typeLocator, + Collection $collection, +- ScopeConfigInterface $config ++ ScopeConfigInterface $config, ++ CollectionFactory $collectionFactory = null, + ) { + $this->mapper = $mapper; + $this->typeLocator = $typeLocator; +- $this->collection = $collection; ++ $this->collectionFactory = $collectionFactory ?? ObjectManager::getInstance()->get(CollectionFactory::class); + $this->config = $config; + } + +@@ -74,12 +79,11 @@ public function __construct( + public function read($scope = null) : array + { + $config = []; +- + if ($this->config->isSetFlag(self::XML_PATH_INCLUDE_DYNAMIC_ATTRIBUTES, ScopeInterface::SCOPE_STORE)) { + $typeNames = $this->mapper->getMappedTypes(Product::ENTITY); + + /** @var Attribute $attribute */ +- foreach ($this->collection->getAttributes() as $attribute) { ++ foreach ($this->collectionFactory->create()->getAttributes() as $attribute) { + $attributeCode = $attribute->getAttributeCode(); + $locatedType = $this->typeLocator->getType($attributeCode, Product::ENTITY) ?: 'String'; + $locatedType = TypeProcessor::NORMALIZED_ANY_TYPE === $locatedType ? 'String' : ucfirst($locatedType); From d82522edfe4aab91c85c53484bb3a07c3939dfb4 Mon Sep 17 00:00:00 2001 From: Shambhu Kumar <83451160+glo72880@users.noreply.github.com> Date: Mon, 20 May 2024 20:19:26 +0530 Subject: [PATCH 11/82] phpcs fix for missing doc (#101) --- src/Test/Functional/Acceptance/AcceptanceCest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Test/Functional/Acceptance/AcceptanceCest.php b/src/Test/Functional/Acceptance/AcceptanceCest.php index c960a58..070482a 100644 --- a/src/Test/Functional/Acceptance/AcceptanceCest.php +++ b/src/Test/Functional/Acceptance/AcceptanceCest.php @@ -7,6 +7,11 @@ namespace Magento\CloudPatches\Test\Functional\Acceptance; +/** + * Abstract AcceptanceCest + * + * @abstract + */ abstract class AcceptanceCest extends AbstractCest { /** From 1a846a3e03cfa3df278bbd559af9527e4cccf84d Mon Sep 17 00:00:00 2001 From: Shambhu Kumar <83451160+glo72880@users.noreply.github.com> Date: Tue, 21 May 2024 18:44:40 +0530 Subject: [PATCH 12/82] Bump 1.0.27 (#103) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 74de6da..05fd2f8 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.0.25", + "version": "1.0.27", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From 000d80800af6f9e32d70faedd5008f21ef686ea8 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi Date: Tue, 23 Jul 2024 16:55:24 -0500 Subject: [PATCH 13/82] ACP2E-3203: Add php 8.3 to the magento-quality-patches --- composer.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 889d668..1549100 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,14 @@ "ext-json": "*", "composer/composer": "^1.9 || ^2.0", "composer/semver": "@stable", + "monolog/monolog": "^1.25||^2.3||^2.7", "symfony/config": "^3.3||^4.4||^5.0||^6.0", "symfony/console": "^2.6||^4.0||^5.0||^6.0", "symfony/dependency-injection": "^3.3||^4.3||^5.0||^6.0", "symfony/process": "^2.1 || ^4.1 || ^5.1 || ^5.4 || ^6.4", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", - "symfony/yaml": "^3.3||^4.0||^5.0||^6.0", - "monolog/monolog": "^1.25||^2.3||^2.7" + "symfony/yaml": "^3.3||^4.0||^5.0||^6.0||^7.0", + "magento/quality-patches": "^1.1.0" }, "require-dev": { "codeception/codeception": "^4.1 || ^5.1", @@ -29,7 +30,7 @@ "codeception/module-db": "^1.0 || ^3.0", "codeception/module-phpbrowser": "^1.0 || ^3.0", "codeception/module-rest": "^1.2 || ^3.0", - "consolidation/robo": "^1.2 || ^3.0", + "consolidation/robo": "^1.2 || ^3.0 || ^5.0", "phpmd/phpmd": "@stable", "phpunit/phpunit": "^8.5 || ^9.5", "squizlabs/php_codesniffer": "^3.0" From 004af2c02dcb17ba2e38779802caf34f3319152d Mon Sep 17 00:00:00 2001 From: Andrii Dimov Date: Fri, 2 Aug 2024 11:15:26 -0500 Subject: [PATCH 14/82] ACPT-1876: Create Cloud quality patch for Application Server known functional issue --- patches.json | 2 +- ..._reader_should_use_factory_for_collection__2.4.7.patch | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/patches.json b/patches.json index c66cf87..7b55679 100644 --- a/patches.json +++ b/patches.json @@ -413,7 +413,7 @@ }, "magento/module-catalog-graph-ql": { "AttributeReader should use Factory for Collection": { - ">=100.4.7 <100.4.7": "ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch" + ">=100.4.7 <100.4.8": "ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch" } } } diff --git a/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch b/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch index 314ebcd..fae4769 100644 --- a/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch +++ b/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch @@ -24,13 +24,13 @@ index ecd83bf61ef0..05acb97e4bd7 100644 use Magento\Framework\GraphQl\Schema\Type\Entity\MapperInterface; @@ -36,9 +38,9 @@ class AttributeReader implements ReaderInterface private Type $typeLocator; - + /** - * @var Collection + * @var CollectionFactory */ - private Collection $collection; -+ private readonly CollectionFactory $collectionFactory; ++ private CollectionFactory $collectionFactory; /** * @var ScopeConfigInterface @@ -58,7 +58,7 @@ index ecd83bf61ef0..05acb97e4bd7 100644 + $this->collectionFactory = $collectionFactory ?? ObjectManager::getInstance()->get(CollectionFactory::class); $this->config = $config; } - + @@ -74,12 +79,11 @@ public function __construct( public function read($scope = null) : array { @@ -66,7 +66,7 @@ index ecd83bf61ef0..05acb97e4bd7 100644 - if ($this->config->isSetFlag(self::XML_PATH_INCLUDE_DYNAMIC_ATTRIBUTES, ScopeInterface::SCOPE_STORE)) { $typeNames = $this->mapper->getMappedTypes(Product::ENTITY); - + /** @var Attribute $attribute */ - foreach ($this->collection->getAttributes() as $attribute) { + foreach ($this->collectionFactory->create()->getAttributes() as $attribute) { From faaeefe3a950154d115810b33988370b6f9c300e Mon Sep 17 00:00:00 2001 From: Siva <57413865+sivaram7@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:34:10 -0500 Subject: [PATCH 15/82] Stabilize functional tests (#111) --- composer.json | 2 +- .../Functional/Acceptance/AbstractCest.php | 128 ++++++++++++++++++ .../Acceptance/Acceptance81Cest.php | 25 ++-- 3 files changed, 146 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 1549100..b369773 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "composer/semver": "@stable", "monolog/monolog": "^1.25||^2.3||^2.7", "symfony/config": "^3.3||^4.4||^5.0||^6.0", - "symfony/console": "^2.6||^4.0||^5.0||^6.0", + "symfony/console": "^2.8 || ^4.0 || ^5.1 || ^5.4 || ^6.4", "symfony/dependency-injection": "^3.3||^4.3||^5.0||^6.0", "symfony/process": "^2.1 || ^4.1 || ^5.1 || ^5.4 || ^6.4", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", diff --git a/src/Test/Functional/Acceptance/AbstractCest.php b/src/Test/Functional/Acceptance/AbstractCest.php index d3374bb..4346122 100644 --- a/src/Test/Functional/Acceptance/AbstractCest.php +++ b/src/Test/Functional/Acceptance/AbstractCest.php @@ -17,6 +17,124 @@ class AbstractCest */ protected $edition = 'EE'; + /** + * @var array + */ + private $dependencyListFor244 = [ + "magento/module-re-captcha-admin-ui" => "1.1.2", + "magento/module-re-captcha-checkout" => "1.1.2", + "magento/module-re-captcha-contact" => "1.1.1", + "magento/module-re-captcha-customer" => "1.1.2", + "magento/module-re-captcha-frontend-ui" => "1.1.2", + "magento/module-re-captcha-migration" => "1.1.2", + "magento/module-re-captcha-newsletter" => "1.1.2", + "magento/module-re-captcha-paypal" => "1.1.2", + "magento/module-re-captcha-review" => "1.1.2", + "magento/module-re-captcha-send-friend" => "1.1.2", + "magento/module-re-captcha-store-pickup" => "1.0.1", + "magento/module-re-captcha-ui" => "1.1.2", + "magento/module-re-captcha-user" => "1.1.2", + "magento/module-re-captcha-validation" => "1.1.1", + "magento/module-re-captcha-validation-api" => "1.1.1", + "magento/module-re-captcha-version-2-checkbox" => "2.0.2", + "magento/module-re-captcha-version-2-invisible" => "2.0.2", + "magento/module-re-captcha-version-3-invisible" => "2.0.2", + "magento/module-re-captcha-webapi-api" => "1.0.1", + "magento/module-re-captcha-webapi-rest" => "1.0.1", + "magento/module-re-captcha-webapi-graph-ql" => "1.0.1", + "magento/module-re-captcha-webapi-ui" => "1.0.1", + "magento/module-securitytxt" => "1.1.1", + "magento/module-two-factor-auth" => "1.1.3", + "magento/module-re-captcha-checkout-sales-rule" => "1.1.0", + "magento/inventory-composer-installer" => "1.2.0", + "magento/module-inventory" => "1.2.2", + "magento/module-inventory-admin-ui" => "1.2.2", + "magento/module-inventory-advanced-checkout" => "1.2.1", + "magento/module-inventory-api" => "1.2.2", + "magento/module-inventory-bundle-product" => "1.2.1", + "magento/module-inventory-bundle-product-admin-ui" => "1.2.2", + "magento/module-inventory-bundle-product-indexer" => "1.1.1", + "magento/module-inventory-bundle-import-export" => "1.1.1", + "magento/module-inventory-cache" => "1.2.2", + "magento/module-inventory-catalog" => "1.2.2", + "magento/module-inventory-catalog-admin-ui" => "1.2.2", + "magento/module-inventory-catalog-api" => "1.3.2", + "magento/module-inventory-catalog-search" => "1.2.2", + "magento/module-inventory-configurable-product" => "1.2.2", + "magento/module-inventory-configurable-product-admin-ui" => "1.2.2", + "magento/module-inventory-configurable-product-indexer" => "1.2.2", + "magento/module-inventory-configuration" => "1.2.2", + "magento/module-inventory-configuration-api" => "1.2.1", + "magento/module-inventory-distance-based-source-selection" => "1.2.2", + "magento/module-inventory-distance-based-source-selection-admin-ui" => "1.2.1", + "magento/module-inventory-distance-based-source-selection-api" => "1.2.1", + "magento/module-inventory-elasticsearch" => "1.2.1", + "magento/module-inventory-export-stock" => "1.2.1", + "magento/module-inventory-export-stock-api" => "1.2.1", + "magento/module-inventory-graph-ql" => "1.2.1", + "magento/module-inventory-grouped-product" => "1.2.2", + "magento/module-inventory-grouped-product-admin-ui" => "1.2.2", + "magento/module-inventory-grouped-product-indexer" => "1.2.2", + "magento/module-inventory-import-export" => "1.2.2", + "magento/module-inventory-indexer" => "2.1.2", + "magento/module-inventory-in-store-pickup" => "1.1.1", + "magento/module-inventory-in-store-pickup-admin-ui" => "1.1.1", + "magento/module-inventory-in-store-pickup-api" => "1.1.1", + "magento/module-inventory-in-store-pickup-frontend" => "1.1.2", + "magento/module-inventory-in-store-pickup-graph-ql" => "1.1.1", + "magento/module-inventory-in-store-pickup-multishipping" => "1.1.1", + "magento/module-inventory-in-store-pickup-quote" => "1.1.1", + "magento/module-inventory-in-store-pickup-quote-graph-ql" => "1.1.1", + "magento/module-inventory-in-store-pickup-sales" => "1.1.1", + "magento/module-inventory-in-store-pickup-sales-admin-ui" => "1.1.2", + "magento/module-inventory-in-store-pickup-sales-api" => "1.1.1", + "magento/module-inventory-in-store-pickup-shipping" => "1.1.1", + "magento/module-inventory-in-store-pickup-shipping-admin-ui" => "1.1.1", + "magento/module-inventory-in-store-pickup-shipping-api" => "1.1.1", + "magento/module-inventory-in-store-pickup-webapi-extension" => "1.1.1", + "magento/module-inventory-low-quantity-notification" => "1.2.1", + "magento/module-inventory-low-quantity-notification-admin-ui" => "1.2.2", + "magento/module-inventory-low-quantity-notification-api" => "1.2.1", + "magento/module-inventory-multi-dimensional-indexer-api" => "1.2.1", + "magento/module-inventory-product-alert" => "1.2.2", + "magento/module-inventory-quote-graph-ql" => "1.0.1", + "magento/module-inventory-requisition-list" => "1.2.2", + "magento/module-inventory-reservation-cli" => "1.2.2", + "magento/module-inventory-reservations" => "1.2.1", + "magento/module-inventory-reservations-api" => "1.2.1", + "magento/module-inventory-sales" => "1.2.2", + "magento/module-inventory-sales-admin-ui" => "1.2.2", + "magento/module-inventory-sales-api" => "1.2.1", + "magento/module-inventory-sales-frontend-ui" => "1.2.2", + "magento/module-inventory-setup-fixture-generator" => "1.2.1", + "magento/module-inventory-shipping" => "1.2.2", + "magento/module-inventory-shipping-admin-ui" => "1.2.2", + "magento/module-inventory-source-deduction-api" => "1.2.2", + "magento/module-inventory-source-selection" => "1.2.1", + "magento/module-inventory-source-selection-api" => "1.4.1", + "magento/module-inventory-visual-merchandiser" => "1.1.2", + "magento/module-inventory-swatches-frontend-ui" => "1.0.1", + "magento/module-inventory-catalog-frontend-ui" => "1.0.2", + "magento/module-inventory-configurable-product-frontend-ui" => "1.0.2", + "magento/module-inventory-wishlist" => "1.0.1", + "magento/module-inventory-catalog-search-bundle-product" => "1.0.1", + "magento/module-inventory-catalog-search-configurable-product" => "1.0.1", + "magento/module-page-builder" => "2.2.2", + "magento/module-page-builder-analytics" => "1.6.2", + "magento/module-cms-page-builder-analytics" => "1.6.2", + "magento/module-page-builder-admin-analytics" => "1.1.2", + "magento/module-catalog-page-builder-analytics" => "1.6.2", + "magento/module-aws-s3-page-builder" => "1.0.2", + "magento/module-banner-page-builder" => "2.2.2", + "magento/module-banner-page-builder-analytics" => "1.7.1", + "magento/module-catalog-staging-page-builder" => "1.7.1", + "magento/module-staging-page-builder" => "2.2.2", + "magento/module-cms-page-builder-analytics-staging" => "1.7.1", + "magento/module-catalog-page-builder-analytics-staging" => "1.7.1", + "magento/module-page-builder-admin-gws-admin-ui" => "1.7.1" + ]; + + /** * @param \CliTester $I */ @@ -29,6 +147,7 @@ public function _before(\CliTester $I): void * @param \CliTester $I * @param string $templateVersion * @param string $magentoVersion + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function prepareTemplate(\CliTester $I, string $templateVersion, string $magentoVersion = null): void { @@ -58,6 +177,15 @@ protected function prepareTemplate(\CliTester $I, string $templateVersion, strin ); } + if ($magentoVersion === '2.4.4') { + foreach ($this->dependencyListFor244 as $package => $version) { + $I->assertTrue( + $I->addDependencyToComposer($package, $version), + "Can not override dependency {$package} with version {$version} for Adobe Commerce 2.4.4" + ); + } + } + if ($this->edition === 'CE' || $magentoVersion) { $version = $magentoVersion ?: $this->getVersionRangeForMagento($I); $I->removeDependencyFromComposer('magento/magento-cloud-metapackage'); diff --git a/src/Test/Functional/Acceptance/Acceptance81Cest.php b/src/Test/Functional/Acceptance/Acceptance81Cest.php index 49ffd69..fa8a4ec 100644 --- a/src/Test/Functional/Acceptance/Acceptance81Cest.php +++ b/src/Test/Functional/Acceptance/Acceptance81Cest.php @@ -18,14 +18,23 @@ class Acceptance81Cest extends AcceptanceCest protected function patchesDataProvider(): array { return [ - ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4'], - ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p1'], - ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p2'], - ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p3'], - ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p4'], - ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5'], - ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5-p1'], - ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5-p2'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p1'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p2'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p3'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p4'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p5'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p6'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p7'], + ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p8'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p1'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p2'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p3'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p4'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p5'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p6'], + ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p7'], ]; } } From 6bd35bf305ea6c8dff9911c1d14971ae401a5e1c Mon Sep 17 00:00:00 2001 From: Andrii Shevtsov Date: Tue, 17 Sep 2024 15:35:29 -0500 Subject: [PATCH 16/82] MCLOUD-12855 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b369773..636d694 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "ext-json": "*", "composer/composer": "^1.9 || ^2.0", "composer/semver": "@stable", - "monolog/monolog": "^1.25||^2.3||^2.7", + "monolog/monolog": "^1.25||^2.3||^2.7|| ^3.6", "symfony/config": "^3.3||^4.4||^5.0||^6.0", "symfony/console": "^2.8 || ^4.0 || ^5.1 || ^5.4 || ^6.4", "symfony/dependency-injection": "^3.3||^4.3||^5.0||^6.0", From 635a6574845c9ffc4ad98b0fe4186bbb6fda69ef Mon Sep 17 00:00:00 2001 From: Ankit Koranne Date: Fri, 20 Sep 2024 13:44:37 +0530 Subject: [PATCH 17/82] Acceptancecest test for 8.1 --- src/Test/Functional/Acceptance/Acceptance81Cest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Test/Functional/Acceptance/Acceptance81Cest.php b/src/Test/Functional/Acceptance/Acceptance81Cest.php index fa8a4ec..383eddc 100644 --- a/src/Test/Functional/Acceptance/Acceptance81Cest.php +++ b/src/Test/Functional/Acceptance/Acceptance81Cest.php @@ -18,6 +18,7 @@ class Acceptance81Cest extends AcceptanceCest protected function patchesDataProvider(): array { return [ + ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p1'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p2'], @@ -27,6 +28,7 @@ protected function patchesDataProvider(): array ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p6'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p7'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p8'], + ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5'], ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5'], ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p1'], ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p2'], From f7c83b28601c48de319941b0846ff65f04f4e036 Mon Sep 17 00:00:00 2001 From: Ankit Koranne Date: Mon, 23 Sep 2024 09:41:57 +0530 Subject: [PATCH 18/82] Acceptancecest test for 8.1 --- src/Test/Functional/Acceptance/Acceptance81Cest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Test/Functional/Acceptance/Acceptance81Cest.php b/src/Test/Functional/Acceptance/Acceptance81Cest.php index 383eddc..fa8a4ec 100644 --- a/src/Test/Functional/Acceptance/Acceptance81Cest.php +++ b/src/Test/Functional/Acceptance/Acceptance81Cest.php @@ -18,7 +18,6 @@ class Acceptance81Cest extends AcceptanceCest protected function patchesDataProvider(): array { return [ - ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p1'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p2'], @@ -28,7 +27,6 @@ protected function patchesDataProvider(): array ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p6'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p7'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p8'], - ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5'], ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5'], ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p1'], ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p2'], From 105bb42745a392493766e5ea8fb2dfb547f1f71d Mon Sep 17 00:00:00 2001 From: Ankit Koranne Date: Tue, 24 Sep 2024 20:56:52 +0530 Subject: [PATCH 19/82] Acceptancecest test for 8.1 --- src/Test/Functional/Acceptance/Acceptance81Cest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Test/Functional/Acceptance/Acceptance81Cest.php b/src/Test/Functional/Acceptance/Acceptance81Cest.php index fa8a4ec..bbd08f3 100644 --- a/src/Test/Functional/Acceptance/Acceptance81Cest.php +++ b/src/Test/Functional/Acceptance/Acceptance81Cest.php @@ -18,7 +18,6 @@ class Acceptance81Cest extends AcceptanceCest protected function patchesDataProvider(): array { return [ - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p1'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p2'], ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p3'], From 4268f6d6fc8bc3eab94da17785b91d6670d053a4 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 15 Oct 2024 20:29:49 +0530 Subject: [PATCH 20/82] MCLOUD-12968: Push B2B isolated patch through Magento Cloud Patch Tool --- patches.json | 3 + ...company_account_create_request_1.3.3.patch | 144 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 patches/B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch diff --git a/patches.json b/patches.json index 7b55679..5ce0992 100644 --- a/patches.json +++ b/patches.json @@ -370,6 +370,9 @@ "magento/magento2-b2b-base": { "Layered navigation filter is present only when product is present on the listing page with enabled Shared catalog": { ">=1.1.5 <1.3.1": "MCLOUD-6923__layered_navigation_filter_is_present_only_when_product_is_present_on_the_listing_page_with_enabled_shared_catalog__2.3.5.patch" + }, + "Fields hydration on company account create request": { + ">=1.3.3 <1.3.3-p11 || >=1.3.4 <1.3.4-p10 || >=1.3.5 <1.3.5-p8 || >=1.4.2 <1.4.2-p3": "B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch" } }, "magento/magento2-ee-base": { diff --git a/patches/B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch b/patches/B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch new file mode 100644 index 0000000..74fa42d --- /dev/null +++ b/patches/B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch @@ -0,0 +1,144 @@ +new file mode 100644 +--- /dev/null ++++ b/vendor/magento/module-company/Model/Customer/AccountManagement/CompanyRequestHydrator.php +@@ -0,0 +1,66 @@ ++request = $request; ++ } ++ ++ /** ++ * Get and hydrate company data from HTTP request. ++ * ++ * @return array ++ */ ++ public function getCompanyDataFromRequest(): array ++ { ++ $result = []; ++ $companyData = $this->request->getPost('company', []); ++ foreach ($this->fieldsToSave as $item) { ++ if (isset($companyData[$item])) { ++ $result[$item] = $companyData[$item]; ++ } ++ } ++ ++ return $result; ++ } ++} +--- a/vendor/magento/module-company/Plugin/Customer/Api/AccountManagement.php ++++ b/vendor/magento/module-company/Plugin/Customer/Api/AccountManagement.php +@@ -11,17 +11,13 @@ + use Magento\Customer\Api\CustomerRepositoryInterface; + use Magento\Company\Api\CompanyManagementInterface; + use Magento\Framework\Exception\NoSuchEntityException; ++use Magento\Company\Model\Customer\AccountManagement\CompanyRequestHydrator; + + /** + * Plugin for AccountManagement. Processing company data. + */ + class AccountManagement + { +- /** +- * @var \Magento\Framework\App\Request\Http +- */ +- private $request; +- + /** + * @var \Magento\Company\Model\Email\Sender + */ +@@ -47,30 +43,35 @@ + */ + private $customerRepository; + ++ /** ++ * @var CompanyRequestHydrator ++ */ ++ private $companyRequestHydrator; ++ + /** + * AccountManagement constructor + * +- * @param \Magento\Framework\App\Request\Http $request + * @param \Magento\Company\Model\Email\Sender $companyEmailSender + * @param \Magento\Backend\Model\UrlInterface $urlBuilder + * @param \Magento\Company\Model\Customer\Company $customerCompany + * @param CompanyManagementInterface $companyManagement + * @param CustomerRepositoryInterface $customerRepository ++ * @param CompanyRequestHydrator $companyRequestHydrator + */ + public function __construct( +- \Magento\Framework\App\Request\Http $request, + \Magento\Company\Model\Email\Sender $companyEmailSender, + \Magento\Backend\Model\UrlInterface $urlBuilder, + \Magento\Company\Model\Customer\Company $customerCompany, + CompanyManagementInterface $companyManagement, +- CustomerRepositoryInterface $customerRepository ++ CustomerRepositoryInterface $customerRepository, ++ CompanyRequestHydrator $companyRequestHydrator + ) { +- $this->request = $request; + $this->companyEmailSender = $companyEmailSender; + $this->urlBuilder = $urlBuilder; + $this->customerCompany = $customerCompany; + $this->companyManagement = $companyManagement; + $this->customerRepository = $customerRepository; ++ $this->companyRequestHydrator = $companyRequestHydrator; + } + + /** +@@ -127,11 +128,7 @@ + \Magento\Customer\Api\AccountManagementInterface $subject, + \Magento\Customer\Api\Data\CustomerInterface $result + ) { +- $companyData = $this->request->getPost('company', []); +- if (isset($companyData['status'])) { +- unset($companyData['status']); +- } +- ++ $companyData = $this->companyRequestHydrator->getCompanyDataFromRequest(); + if (is_array($companyData) && !empty($companyData)) { + $jobTitle = $companyData['job_title'] ?? null; + $companyDataObject = $this->customerCompany->createCompany($result, $companyData, $jobTitle); \ No newline at end of file From d58fa7d971e089e8c3a28cfcee3c6b8a68fde7ba Mon Sep 17 00:00:00 2001 From: glo42671 Date: Wed, 16 Oct 2024 09:47:42 +0530 Subject: [PATCH 21/82] MCLOUD-12968: Push B2B isolated patch through Magento Cloud Patch Tool --- ..._fields_hydration_company_account_create_request__1.3.3.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patches/{B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch => B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch} (100%) diff --git a/patches/B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch b/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch similarity index 100% rename from patches/B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch rename to patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch From d8150c5f3a2d82977688bfcf983c627f510cc9ee Mon Sep 17 00:00:00 2001 From: glo42671 Date: Wed, 16 Oct 2024 20:15:30 +0530 Subject: [PATCH 22/82] MCLOUD-12968: Push B2B isolated patch through Magento Cloud Patch Tool --- patches.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches.json b/patches.json index 5ce0992..637d3e1 100644 --- a/patches.json +++ b/patches.json @@ -372,7 +372,7 @@ ">=1.1.5 <1.3.1": "MCLOUD-6923__layered_navigation_filter_is_present_only_when_product_is_present_on_the_listing_page_with_enabled_shared_catalog__2.3.5.patch" }, "Fields hydration on company account create request": { - ">=1.3.3 <1.3.3-p11 || >=1.3.4 <1.3.4-p10 || >=1.3.5 <1.3.5-p8 || >=1.4.2 <1.4.2-p3": "B2B-4051_fields_hydration_company_account_create_request_1.3.3.patch" + ">=1.3.3 <1.3.3-p11 || >=1.3.4 <1.3.4-p10 || >=1.3.5 <1.3.5-p8 || >=1.4.2 <1.4.2-p3": "B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch" } }, "magento/magento2-ee-base": { From 3d9720a5dd48eba70bad3fbe0622a1652cb00a42 Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Wed, 16 Oct 2024 21:12:41 +0530 Subject: [PATCH 23/82] MAGECLOUD-12969: security fix patch --- patches.json | 3 + ...for_CVE_2024_34102_CosmicSting_2.4.7.patch | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch diff --git a/patches.json b/patches.json index 7b55679..351632d 100644 --- a/patches.json +++ b/patches.json @@ -280,6 +280,9 @@ }, "Enhanced Layout Cache Efficiency (memory usage reduced)": { ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" + }, + "Patch for CVE-2024-34102 - CosmicSting": { + ">2.4.6 <=2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch new file mode 100644 index 0000000..cf9af8b --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch @@ -0,0 +1,55 @@ +diff --git a/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv b/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv +index 2708988e731..885d0056d4b 100644 +--- a/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv ++++ b/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv +@@ -547,3 +547,4 @@ Dashboard,Dashboard + "Web Section","Web Section" + "Store Email Addresses Section","Store Email Addresses Section" + "Email to a Friend","Email to a Friend" ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +index 025866f654d..cc02ab5ac90 100644 +--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +@@ -439,3 +439,4 @@ Summary,Summary + Test,Test + test,test + Two,Two ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +index e80cb58e679..3d0e8ab2650 100644 +--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +@@ -489,3 +489,4 @@ Remove,Remove + Test,Test + test,test + Two,Two ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +index cd7960409e1..df31058ff32 100644 +--- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php ++++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +@@ -278,6 +278,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface, ResetAf + // convert to string directly to avoid situations when $className is object + // which implements __toString method like \ReflectionObject + $className = (string) $className; ++ if (is_subclass_of($className, \SimpleXMLElement::class) ++ || is_subclass_of($className, \DOMElement::class)) { ++ throw new SerializationException( ++ new Phrase('Invalid data type') ++ ); ++ } + $class = new ClassReflection($className); + if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { + $className = substr($className, 0, -strlen('Interface')); +diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php +--- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) ++++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) +@@ -35,6 +35,7 @@ + public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) + { + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); ++ $this->keys = [end($this->keys)]; + //Making sure keys are large enough. + foreach ($this->keys as &$key) { + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file From 4533a33bdfe14e8dc6523ea781923ee690332153 Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Wed, 16 Oct 2024 21:28:27 +0530 Subject: [PATCH 24/82] MAGECLOUD-12969: security fix patch --- patches.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches.json b/patches.json index 351632d..227df61 100644 --- a/patches.json +++ b/patches.json @@ -282,7 +282,7 @@ ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" }, "Patch for CVE-2024-34102 - CosmicSting": { - ">2.4.6 <=2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch" + ">=2.4.4 <=2.4.8-beta1": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch" } }, "magento/module-paypal": { From 64787023f22d5f56b543f5cda12dbe42f54ed552 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Wed, 16 Oct 2024 13:00:34 -0500 Subject: [PATCH 25/82] update log file path --- src/Test/Functional/Acceptance/PatchApplierCest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Test/Functional/Acceptance/PatchApplierCest.php b/src/Test/Functional/Acceptance/PatchApplierCest.php index 05a0990..f5e58e1 100644 --- a/src/Test/Functional/Acceptance/PatchApplierCest.php +++ b/src/Test/Functional/Acceptance/PatchApplierCest.php @@ -42,7 +42,7 @@ public function testApplyingPatch(\CliTester $I): void $targetFile = $I->grabFileContent('/target_file.md', Docker::BUILD_CONTAINER); $I->assertStringContainsString('# Hello Magento', $targetFile); $I->assertStringContainsString('## Additional Info', $targetFile); - $log = $I->grabFileContent('/var/log/cloud.log', Docker::BUILD_CONTAINER); + $log = $I->grabFileContent('/init/var/log/cloud.log', Docker::BUILD_CONTAINER); $I->assertStringContainsString('Patch ../m2-hotfixes/patch.patch has been applied', $log); } @@ -65,7 +65,7 @@ public function testApplyingExistingPatch(\CliTester $I): void $I->assertStringContainsString('## Additional Info', $targetFile); $I->assertStringContainsString( 'Patch ../m2-hotfixes/patch.patch was already applied', - $I->grabFileContent('/var/log/cloud.log', Docker::BUILD_CONTAINER) + $I->grabFileContent('/init/var/log/cloud.log', Docker::BUILD_CONTAINER) ); } } From 4343466b03e5336cac81c7d5c5f0c55f6af190ca Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Wed, 16 Oct 2024 13:16:37 -0500 Subject: [PATCH 26/82] fix codesniffer issue --- src/Test/Functional/Acceptance/AbstractCest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Test/Functional/Acceptance/AbstractCest.php b/src/Test/Functional/Acceptance/AbstractCest.php index 4346122..07c9c21 100644 --- a/src/Test/Functional/Acceptance/AbstractCest.php +++ b/src/Test/Functional/Acceptance/AbstractCest.php @@ -134,7 +134,6 @@ class AbstractCest "magento/module-page-builder-admin-gws-admin-ui" => "1.7.1" ]; - /** * @param \CliTester $I */ From 2b5fa2260eb0a76c19e4adccd2a72316c820f80d Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Thu, 17 Oct 2024 21:12:39 +0530 Subject: [PATCH 27/82] MAGECLOUD-12969: security fix patch --- patches.json | 5 +- ...or_CVE_2024_34102_CosmicSting__2.4.4.patch | 62 +++++++++++++++++++ ...or_CVE_2024_34102_CosmicSting__2.4.5.patch | 62 +++++++++++++++++++ ...or_CVE_2024_34102_CosmicSting__2.4.6.patch | 46 ++++++++++++++ ...r_CVE_2024_34102_CosmicSting__2.4.7.patch} | 0 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch rename patches/{MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch => MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch} (100%) diff --git a/patches.json b/patches.json index 227df61..4069224 100644 --- a/patches.json +++ b/patches.json @@ -282,7 +282,10 @@ ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" }, "Patch for CVE-2024-34102 - CosmicSting": { - ">=2.4.4 <=2.4.8-beta1": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch" + ">=2.4.7 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch", + ">=2.4.6 <2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", + ">=2.4.5 <2.4.6": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", + ">=2.4.4 <2.4.5": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch new file mode 100644 index 0000000..53317da --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch @@ -0,0 +1,62 @@ +diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +index a491a567a37..5e8bef787d2 100644 +--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +@@ -4,3 +4,4 @@ Summary,Summary + Menu,Menu + Account,Account + Settings,Settings ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +index 7bf9e0afaf0..00493cc05ba 100644 +--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +@@ -54,3 +54,4 @@ Footer,Footer + "Update to your %store_name shipment","Update to your %store_name shipment" + "Address Book","Address Book" + "Account Information","Account Information" ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +index 908a4e70140..cc019845b58 100644 +--- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php ++++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @return \Magento\Framework\Reflection\NameFinder + * + * @deprecated 100.1.0 ++ * @see nothing + */ + private function getNameFinder() + { +@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @throws \Exception + * @throws SerializationException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) ++ * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function _createFromArray($className, $data) + { +@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + // convert to string directly to avoid situations when $className is object + // which implements __toString method like \ReflectionObject + $className = (string) $className; ++ if (is_subclass_of($className, \SimpleXMLElement::class) ++ || is_subclass_of($className, \DOMElement::class)) { ++ throw new SerializationException( ++ new Phrase('Invalid data type') ++ ); ++ } + $class = new ClassReflection($className); + if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { + $className = substr($className, 0, -strlen('Interface')); +diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php +--- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) ++++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) +@@ -35,6 +35,7 @@ + public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) + { + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); ++ $this->keys = [end($this->keys)]; + //Making sure keys are large enough. + foreach ($this->keys as &$key) { + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch new file mode 100644 index 0000000..3ea35c2 --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch @@ -0,0 +1,62 @@ +diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +index a491a567a37..5e8bef787d2 100644 +--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +@@ -4,3 +4,4 @@ Summary,Summary + Menu,Menu + Account,Account + Settings,Settings ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +index 7bf9e0afaf0..00493cc05ba 100644 +--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +@@ -54,3 +54,4 @@ Footer,Footer + "Update to your %store_name shipment","Update to your %store_name shipment" + "Address Book","Address Book" + "Account Information","Account Information" ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +index a5e881f4be5..a60f1dd7ba1 100644 +--- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php ++++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @return \Magento\Framework\Reflection\NameFinder + * + * @deprecated 100.1.0 ++ * @see nothing + */ + private function getNameFinder() + { +@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @throws \Exception + * @throws SerializationException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) ++ * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function _createFromArray($className, $data) + { +@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + // convert to string directly to avoid situations when $className is object + // which implements __toString method like \ReflectionObject + $className = (string) $className; ++ if (is_subclass_of($className, \SimpleXMLElement::class) ++ || is_subclass_of($className, \DOMElement::class)) { ++ throw new SerializationException( ++ new Phrase('Invalid data type') ++ ); ++ } + $class = new ClassReflection($className); + if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { + $className = substr($className, 0, -strlen('Interface')); +diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php +--- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) ++++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) +@@ -35,6 +35,7 @@ + public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) + { + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); ++ $this->keys = [end($this->keys)]; + //Making sure keys are large enough. + foreach ($this->keys as &$key) { + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch new file mode 100644 index 0000000..1b7d545 --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch @@ -0,0 +1,46 @@ +diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +index a491a567a37..5e8bef787d2 100644 +--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +@@ -4,3 +4,4 @@ Summary,Summary + Menu,Menu + Account,Account + Settings,Settings ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +index 7bf9e0afaf0..00493cc05ba 100644 +--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +@@ -54,3 +54,4 @@ Footer,Footer + "Update to your %store_name shipment","Update to your %store_name shipment" + "Address Book","Address Book" + "Account Information","Account Information" ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +index 9d7fd443508..65987772c23 100644 +--- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php ++++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +@@ -275,6 +275,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + // convert to string directly to avoid situations when $className is object + // which implements __toString method like \ReflectionObject + $className = (string) $className; ++ if (is_subclass_of($className, \SimpleXMLElement::class) ++ || is_subclass_of($className, \DOMElement::class)) { ++ throw new SerializationException( ++ new Phrase('Invalid data type') ++ ); ++ } + $class = new ClassReflection($className); + if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { + $className = substr($className, 0, -strlen('Interface')); +diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php +--- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) ++++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) +@@ -35,6 +35,7 @@ + public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) + { + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); ++ $this->keys = [end($this->keys)]; + //Making sure keys are large enough. + foreach ($this->keys as &$key) { + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch similarity index 100% rename from patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting_2.4.7.patch rename to patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch From 8257745b9700ef14c0064c579240a3fab10669c6 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Thu, 17 Oct 2024 14:06:40 -0500 Subject: [PATCH 28/82] add new line --- ...fields_hydration_company_account_create_request__1.3.3.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch b/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch index 74fa42d..c17eab6 100644 --- a/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch +++ b/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch @@ -141,4 +141,4 @@ new file mode 100644 + $companyData = $this->companyRequestHydrator->getCompanyDataFromRequest(); if (is_array($companyData) && !empty($companyData)) { $jobTitle = $companyData['job_title'] ?? null; - $companyDataObject = $this->customerCompany->createCompany($result, $companyData, $jobTitle); \ No newline at end of file + $companyDataObject = $this->customerCompany->createCompany($result, $companyData, $jobTitle); From c68d8964df6d9c7c225118d216ed37f21d3c0395 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Thu, 17 Oct 2024 14:09:05 -0500 Subject: [PATCH 29/82] add new line --- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch index 53317da..5175edb 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch @@ -59,4 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch index 3ea35c2..e5740c2 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch @@ -59,4 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch index 1b7d545..1296cc0 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch @@ -43,4 +43,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch index cf9af8b..4f23fb7 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch @@ -52,4 +52,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); From 784caccdc38cc62e287fb8442b00121fbd50fbaa Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Fri, 18 Oct 2024 15:57:40 +0530 Subject: [PATCH 30/82] MAGECLOUD-12969: security fix patch --- patches.json | 5 +- ...or_CVE_2024_34102_CosmicSting__2.4.4.patch | 62 ------------------- ...or_CVE_2024_34102_CosmicSting__2.4.5.patch | 62 ------------------- ...or_CVE_2024_34102_CosmicSting__2.4.6.patch | 46 -------------- 4 files changed, 1 insertion(+), 174 deletions(-) delete mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch delete mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch delete mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch diff --git a/patches.json b/patches.json index 4069224..15f30e2 100644 --- a/patches.json +++ b/patches.json @@ -282,10 +282,7 @@ ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" }, "Patch for CVE-2024-34102 - CosmicSting": { - ">=2.4.7 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch", - ">=2.4.6 <2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", - ">=2.4.5 <2.4.6": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", - ">=2.4.4 <2.4.5": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch" + ">=2.4.4 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch deleted file mode 100644 index 53317da..0000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -index a491a567a37..5e8bef787d2 100644 ---- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -@@ -4,3 +4,4 @@ Summary,Summary - Menu,Menu - Account,Account - Settings,Settings -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -index 7bf9e0afaf0..00493cc05ba 100644 ---- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -@@ -54,3 +54,4 @@ Footer,Footer - "Update to your %store_name shipment","Update to your %store_name shipment" - "Address Book","Address Book" - "Account Information","Account Information" -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -index 908a4e70140..cc019845b58 100644 ---- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php -+++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @return \Magento\Framework\Reflection\NameFinder - * - * @deprecated 100.1.0 -+ * @see nothing - */ - private function getNameFinder() - { -@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @throws \Exception - * @throws SerializationException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) -+ * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function _createFromArray($className, $data) - { -@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - // convert to string directly to avoid situations when $className is object - // which implements __toString method like \ReflectionObject - $className = (string) $className; -+ if (is_subclass_of($className, \SimpleXMLElement::class) -+ || is_subclass_of($className, \DOMElement::class)) { -+ throw new SerializationException( -+ new Phrase('Invalid data type') -+ ); -+ } - $class = new ClassReflection($className); - if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { - $className = substr($className, 0, -strlen('Interface')); -diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php ---- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) -+++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) -@@ -35,6 +35,7 @@ - public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) - { - $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); -+ $this->keys = [end($this->keys)]; - //Making sure keys are large enough. - foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch deleted file mode 100644 index 3ea35c2..0000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -index a491a567a37..5e8bef787d2 100644 ---- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -@@ -4,3 +4,4 @@ Summary,Summary - Menu,Menu - Account,Account - Settings,Settings -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -index 7bf9e0afaf0..00493cc05ba 100644 ---- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -@@ -54,3 +54,4 @@ Footer,Footer - "Update to your %store_name shipment","Update to your %store_name shipment" - "Address Book","Address Book" - "Account Information","Account Information" -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -index a5e881f4be5..a60f1dd7ba1 100644 ---- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php -+++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @return \Magento\Framework\Reflection\NameFinder - * - * @deprecated 100.1.0 -+ * @see nothing - */ - private function getNameFinder() - { -@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @throws \Exception - * @throws SerializationException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) -+ * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function _createFromArray($className, $data) - { -@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - // convert to string directly to avoid situations when $className is object - // which implements __toString method like \ReflectionObject - $className = (string) $className; -+ if (is_subclass_of($className, \SimpleXMLElement::class) -+ || is_subclass_of($className, \DOMElement::class)) { -+ throw new SerializationException( -+ new Phrase('Invalid data type') -+ ); -+ } - $class = new ClassReflection($className); - if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { - $className = substr($className, 0, -strlen('Interface')); -diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php ---- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) -+++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) -@@ -35,6 +35,7 @@ - public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) - { - $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); -+ $this->keys = [end($this->keys)]; - //Making sure keys are large enough. - foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch deleted file mode 100644 index 1b7d545..0000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch +++ /dev/null @@ -1,46 +0,0 @@ -diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -index a491a567a37..5e8bef787d2 100644 ---- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -@@ -4,3 +4,4 @@ Summary,Summary - Menu,Menu - Account,Account - Settings,Settings -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -index 7bf9e0afaf0..00493cc05ba 100644 ---- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -@@ -54,3 +54,4 @@ Footer,Footer - "Update to your %store_name shipment","Update to your %store_name shipment" - "Address Book","Address Book" - "Account Information","Account Information" -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -index 9d7fd443508..65987772c23 100644 ---- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php -+++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -@@ -275,6 +275,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - // convert to string directly to avoid situations when $className is object - // which implements __toString method like \ReflectionObject - $className = (string) $className; -+ if (is_subclass_of($className, \SimpleXMLElement::class) -+ || is_subclass_of($className, \DOMElement::class)) { -+ throw new SerializationException( -+ new Phrase('Invalid data type') -+ ); -+ } - $class = new ClassReflection($className); - if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { - $className = substr($className, 0, -strlen('Interface')); -diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php ---- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) -+++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) -@@ -35,6 +35,7 @@ - public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) - { - $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); -+ $this->keys = [end($this->keys)]; - //Making sure keys are large enough. - foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file From 9bc92a425ee57b40e83d00b03c57c55eef907b73 Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Mon, 21 Oct 2024 23:29:56 +0530 Subject: [PATCH 31/82] MAGECLOUD-12969: security fix patch --- patches.json | 11 +- ...or_CVE_2024_34102_CosmicSting__2.4.4.patch | 62 +++++++ ...or_CVE_2024_34102_CosmicSting__2.4.5.patch | 62 +++++++ ...or_CVE_2024_34102_CosmicSting__2.4.6.patch | 46 +++++ ...or_CVE_2024_34102_KeyRotation__2.4.4.patch | 163 ++++++++++++++++++ ...or_CVE_2024_34102_KeyRotation__2.4.5.patch | 162 +++++++++++++++++ ...or_CVE_2024_34102_KeyRotation__2.4.6.patch | 159 +++++++++++++++++ ...or_CVE_2024_34102_KeyRotation__2.4.7.patch | 157 +++++++++++++++++ 8 files changed, 821 insertions(+), 1 deletion(-) create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch create mode 100644 patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch diff --git a/patches.json b/patches.json index 15f30e2..710ec97 100644 --- a/patches.json +++ b/patches.json @@ -282,7 +282,16 @@ ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" }, "Patch for CVE-2024-34102 - CosmicSting": { - ">=2.4.4 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" + ">=2.4.4 <2.4.5": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch", + ">=2.4.5 <2.4.6": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", + ">=2.4.6 <2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", + ">=2.4.7 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" + }, + "Patch for CVE-2024-34102 - KeyRotation": { + ">=2.4.4 <2.4.5": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch", + ">=2.4.5 <2.4.6": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch", + ">=2.4.6 <2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch", + ">=2.4.7 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch new file mode 100644 index 0000000..53317da --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch @@ -0,0 +1,62 @@ +diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +index a491a567a37..5e8bef787d2 100644 +--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +@@ -4,3 +4,4 @@ Summary,Summary + Menu,Menu + Account,Account + Settings,Settings ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +index 7bf9e0afaf0..00493cc05ba 100644 +--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +@@ -54,3 +54,4 @@ Footer,Footer + "Update to your %store_name shipment","Update to your %store_name shipment" + "Address Book","Address Book" + "Account Information","Account Information" ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +index 908a4e70140..cc019845b58 100644 +--- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php ++++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @return \Magento\Framework\Reflection\NameFinder + * + * @deprecated 100.1.0 ++ * @see nothing + */ + private function getNameFinder() + { +@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @throws \Exception + * @throws SerializationException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) ++ * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function _createFromArray($className, $data) + { +@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + // convert to string directly to avoid situations when $className is object + // which implements __toString method like \ReflectionObject + $className = (string) $className; ++ if (is_subclass_of($className, \SimpleXMLElement::class) ++ || is_subclass_of($className, \DOMElement::class)) { ++ throw new SerializationException( ++ new Phrase('Invalid data type') ++ ); ++ } + $class = new ClassReflection($className); + if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { + $className = substr($className, 0, -strlen('Interface')); +diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php +--- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) ++++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) +@@ -35,6 +35,7 @@ + public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) + { + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); ++ $this->keys = [end($this->keys)]; + //Making sure keys are large enough. + foreach ($this->keys as &$key) { + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch new file mode 100644 index 0000000..3ea35c2 --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch @@ -0,0 +1,62 @@ +diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +index a491a567a37..5e8bef787d2 100644 +--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +@@ -4,3 +4,4 @@ Summary,Summary + Menu,Menu + Account,Account + Settings,Settings ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +index 7bf9e0afaf0..00493cc05ba 100644 +--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +@@ -54,3 +54,4 @@ Footer,Footer + "Update to your %store_name shipment","Update to your %store_name shipment" + "Address Book","Address Book" + "Account Information","Account Information" ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +index a5e881f4be5..a60f1dd7ba1 100644 +--- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php ++++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @return \Magento\Framework\Reflection\NameFinder + * + * @deprecated 100.1.0 ++ * @see nothing + */ + private function getNameFinder() + { +@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + * @throws \Exception + * @throws SerializationException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) ++ * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function _createFromArray($className, $data) + { +@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + // convert to string directly to avoid situations when $className is object + // which implements __toString method like \ReflectionObject + $className = (string) $className; ++ if (is_subclass_of($className, \SimpleXMLElement::class) ++ || is_subclass_of($className, \DOMElement::class)) { ++ throw new SerializationException( ++ new Phrase('Invalid data type') ++ ); ++ } + $class = new ClassReflection($className); + if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { + $className = substr($className, 0, -strlen('Interface')); +diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php +--- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) ++++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) +@@ -35,6 +35,7 @@ + public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) + { + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); ++ $this->keys = [end($this->keys)]; + //Making sure keys are large enough. + foreach ($this->keys as &$key) { + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch new file mode 100644 index 0000000..1b7d545 --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch @@ -0,0 +1,46 @@ +diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +index a491a567a37..5e8bef787d2 100644 +--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv +@@ -4,3 +4,4 @@ Summary,Summary + Menu,Menu + Account,Account + Settings,Settings ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +index 7bf9e0afaf0..00493cc05ba 100644 +--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv ++++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv +@@ -54,3 +54,4 @@ Footer,Footer + "Update to your %store_name shipment","Update to your %store_name shipment" + "Address Book","Address Book" + "Account Information","Account Information" ++"Invalid data type","Invalid data type" +diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +index 9d7fd443508..65987772c23 100644 +--- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php ++++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php +@@ -275,6 +275,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface + // convert to string directly to avoid situations when $className is object + // which implements __toString method like \ReflectionObject + $className = (string) $className; ++ if (is_subclass_of($className, \SimpleXMLElement::class) ++ || is_subclass_of($className, \DOMElement::class)) { ++ throw new SerializationException( ++ new Phrase('Invalid data type') ++ ); ++ } + $class = new ClassReflection($className); + if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { + $className = substr($className, 0, -strlen('Interface')); +diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php +--- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) ++++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) +@@ -35,6 +35,7 @@ + public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) + { + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); ++ $this->keys = [end($this->keys)]; + //Making sure keys are large enough. + foreach ($this->keys as &$key) { + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch new file mode 100644 index 0000000..83b15a8 --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch @@ -0,0 +1,163 @@ +diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +new file mode 100644 +index 0000000000000..8777f99139edc +--- /dev/null ++++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +@@ -0,0 +1,141 @@ ++encryptor = $encryptor; ++ $this->cache = $cache; ++ $this->writer = $writer; ++ $this->random = $random; ++ ++ parent::__construct(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function configure() ++ { ++ $this->setName('encryption:key:change'); ++ $this->setDescription('Change the encryption key inside the env.php file.'); ++ $this->addOption( ++ 'key', ++ 'k', ++ InputOption::VALUE_OPTIONAL, ++ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' ++ ); ++ ++ parent::configure(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function execute(InputInterface $input, OutputInterface $output) ++ { ++ try { ++ $key = $input->getOption('key'); ++ ++ if (!empty($key)) { ++ $this->encryptor->validateKey($key); ++ } ++ ++ $this->updateEncryptionKey($key); ++ $this->cache->clean(); ++ ++ $output->writeln('Encryption key has been updated successfully.'); ++ ++ return Cli::RETURN_SUCCESS; ++ } catch (\Exception $e) { ++ $output->writeln('' . $e->getMessage() . ''); ++ return Cli::RETURN_FAILURE; ++ } ++ } ++ ++ /** ++ * Update encryption key ++ * ++ * @param string|null $key ++ * @return void ++ * @throws FileSystemException ++ */ ++ private function updateEncryptionKey(string $key = null): void ++ { ++ // prepare new key, encryptor and new configuration segment ++ if (!$this->writer->checkIfWritable()) { ++ throw new FileSystemException(__('Deployment configuration file is not writable.')); ++ } ++ ++ if (null === $key) { ++ // md5() here is not for cryptographic use. It used for generate encryption key itself ++ // and do not encrypt any passwords ++ // phpcs:ignore Magento2.Security.InsecureFunction ++ $key = md5($this->random->getRandomString(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE)); ++ } ++ ++ $this->encryptor->setNewKey($key); ++ ++ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); ++ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); ++ ++ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; ++ ++ $this->writer->saveConfig($configData); ++ } ++} +diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml +index b4e471f4e40ef..495234759a7f8 100644 +--- a/vendor/magento/module-encryption-key/etc/di.xml ++++ b/vendor/magento/module-encryption-key/etc/di.xml +@@ -11,4 +11,11 @@ + Magento\Config\Model\Config\Structure\Proxy + + ++ ++ ++ ++ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand ++ ++ ++ + \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch new file mode 100644 index 0000000..26c492e --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch @@ -0,0 +1,162 @@ +diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +new file mode 100644 +index 0000000000000..351379552e104 +--- /dev/null ++++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +@@ -0,0 +1,140 @@ ++encryptor = $encryptor; ++ $this->cache = $cache; ++ $this->writer = $writer; ++ $this->random = $random; ++ ++ parent::__construct(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function configure() ++ { ++ $this->setName('encryption:key:change'); ++ $this->setDescription('Change the encryption key inside the env.php file.'); ++ $this->addOption( ++ 'key', ++ 'k', ++ InputOption::VALUE_OPTIONAL, ++ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' ++ ); ++ ++ parent::configure(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function execute(InputInterface $input, OutputInterface $output) ++ { ++ try { ++ $key = $input->getOption('key'); ++ ++ if (!empty($key)) { ++ $this->encryptor->validateKey($key); ++ } ++ ++ $this->updateEncryptionKey($key); ++ $this->cache->clean(); ++ ++ $output->writeln('Encryption key has been updated successfully.'); ++ ++ return Cli::RETURN_SUCCESS; ++ } catch (\Exception $e) { ++ $output->writeln('' . $e->getMessage() . ''); ++ return Cli::RETURN_FAILURE; ++ } ++ } ++ ++ /** ++ * Update encryption key ++ * ++ * @param string|null $key ++ * @return void ++ * @throws FileSystemException ++ */ ++ private function updateEncryptionKey(string $key = null): void ++ { ++ // prepare new key, encryptor and new configuration segment ++ if (!$this->writer->checkIfWritable()) { ++ throw new FileSystemException(__('Deployment configuration file is not writable.')); ++ } ++ ++ if (null === $key) { ++ // md5() here is not for cryptographic use. It used for generate encryption key itself ++ // and do not encrypt any passwords ++ // phpcs:ignore Magento2.Security.InsecureFunction ++ $key = md5($this->random->getRandomString(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE)); ++ } ++ $this->encryptor->setNewKey($key); ++ ++ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); ++ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); ++ ++ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; ++ ++ $this->writer->saveConfig($configData); ++ } ++} +diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml +index b4e471f4e40ef..495234759a7f8 100644 +--- a/vendor/magento/module-encryption-key/etc/di.xml ++++ b/vendor/magento/module-encryption-key/etc/di.xml +@@ -11,4 +11,11 @@ + Magento\Config\Model\Config\Structure\Proxy + + ++ ++ ++ ++ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand ++ ++ ++ + \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch new file mode 100644 index 0000000..4f2178b --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch @@ -0,0 +1,159 @@ +diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +new file mode 100644 +index 0000000000000..0e4995b847893 +--- /dev/null ++++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +@@ -0,0 +1,137 @@ ++encryptor = $encryptor; ++ $this->cache = $cache; ++ $this->writer = $writer; ++ $this->random = $random; ++ ++ parent::__construct(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function configure() ++ { ++ $this->setName('encryption:key:change'); ++ $this->setDescription('Change the encryption key inside the env.php file.'); ++ $this->addOption( ++ 'key', ++ 'k', ++ InputOption::VALUE_OPTIONAL, ++ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' ++ ); ++ ++ parent::configure(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function execute(InputInterface $input, OutputInterface $output) ++ { ++ try { ++ $key = $input->getOption('key'); ++ ++ if (!empty($key)) { ++ $this->encryptor->validateKey($key); ++ } ++ ++ $this->updateEncryptionKey($key); ++ $this->cache->clean(); ++ ++ $output->writeln('Encryption key has been updated successfully.'); ++ ++ return Command::SUCCESS; ++ } catch (\Exception $e) { ++ $output->writeln('' . $e->getMessage() . ''); ++ return Command::FAILURE; ++ } ++ } ++ ++ /** ++ * Update encryption key ++ * ++ * @param string|null $key ++ * @return void ++ * @throws FileSystemException ++ */ ++ private function updateEncryptionKey(string $key = null): void ++ { ++ // prepare new key, encryptor and new configuration segment ++ if (!$this->writer->checkIfWritable()) { ++ throw new FileSystemException(__('Deployment configuration file is not writable.')); ++ } ++ ++ if (null === $key) { ++ // md5() here is not for cryptographic use. It used for generate encryption key itself ++ // and do not encrypt any passwords ++ // phpcs:ignore Magento2.Security.InsecureFunction ++ $key = md5($this->random->getRandomString(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE)); ++ } ++ ++ $this->encryptor->setNewKey($key); ++ ++ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); ++ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); ++ ++ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; ++ ++ $this->writer->saveConfig($configData); ++ } ++} +diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml +index b4e471f4e40ef..495234759a7f8 100644 +--- a/vendor/magento/module-encryption-key/etc/di.xml ++++ b/vendor/magento/module-encryption-key/etc/di.xml +@@ -11,4 +11,11 @@ + Magento\Config\Model\Config\Structure\Proxy + + ++ ++ ++ ++ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand ++ ++ ++ + \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch new file mode 100644 index 0000000..0c8e1fc --- /dev/null +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch @@ -0,0 +1,157 @@ +diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +new file mode 100644 +index 0000000000000..cd6ffb4323163 +--- /dev/null ++++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php +@@ -0,0 +1,135 @@ ++encryptor = $encryptor; ++ $this->cache = $cache; ++ $this->writer = $writer; ++ $this->random = $random; ++ ++ parent::__construct(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function configure() ++ { ++ $this->setName('encryption:key:change'); ++ $this->setDescription('Change the encryption key inside the env.php file.'); ++ $this->addOption( ++ 'key', ++ 'k', ++ InputOption::VALUE_OPTIONAL, ++ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' ++ ); ++ ++ parent::configure(); ++ } ++ ++ /** ++ * @inheritDoc ++ */ ++ protected function execute(InputInterface $input, OutputInterface $output) ++ { ++ try { ++ $key = $input->getOption('key'); ++ ++ if (!empty($key)) { ++ $this->encryptor->validateKey($key); ++ } ++ ++ $this->updateEncryptionKey($key); ++ $this->cache->clean(); ++ ++ $output->writeln('Encryption key has been updated successfully.'); ++ ++ return Command::SUCCESS; ++ } catch (\Exception $e) { ++ $output->writeln('' . $e->getMessage() . ''); ++ return Command::FAILURE; ++ } ++ } ++ ++ /** ++ * Update encryption key ++ * ++ * @param string|null $key ++ * @return void ++ * @throws FileSystemException ++ */ ++ private function updateEncryptionKey(string $key = null): void ++ { ++ // prepare new key, encryptor and new configuration segment ++ if (!$this->writer->checkIfWritable()) { ++ throw new FileSystemException(__('Deployment configuration file is not writable.')); ++ } ++ ++ if (null === $key) { ++ $key = ConfigOptionsListConstants::STORE_KEY_ENCODED_RANDOM_STRING_PREFIX . ++ $this->random->getRandomBytes(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE); ++ } ++ ++ $this->encryptor->setNewKey($key); ++ ++ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); ++ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); ++ ++ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; ++ ++ $this->writer->saveConfig($configData); ++ } ++} +diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml +index b4e471f4e40ef..495234759a7f8 100644 +--- a/vendor/magento/module-encryption-key/etc/di.xml ++++ b/vendor/magento/module-encryption-key/etc/di.xml +@@ -11,4 +11,11 @@ + Magento\Config\Model\Config\Structure\Proxy + + ++ ++ ++ ++ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand ++ ++ ++ + From e1884afc6de967cf95dbfd06cc56253a3212acfd Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Wed, 23 Oct 2024 21:12:54 +0530 Subject: [PATCH 32/82] MAGECLOUD-12969: security fix patch --- patches.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/patches.json b/patches.json index 710ec97..9c990f1 100644 --- a/patches.json +++ b/patches.json @@ -282,16 +282,16 @@ ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" }, "Patch for CVE-2024-34102 - CosmicSting": { - ">=2.4.4 <2.4.5": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch", - ">=2.4.5 <2.4.6": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", - ">=2.4.6 <2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", - ">=2.4.7 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" + ">=2.4.4 <2.4.4-p10": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch", + ">=2.4.5 <2.4.5-p9": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", + ">=2.4.6 <2.4.6-p7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", + ">=2.4.7 <2.4.7-p1": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" }, "Patch for CVE-2024-34102 - KeyRotation": { - ">=2.4.4 <2.4.5": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch", - ">=2.4.5 <2.4.6": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch", - ">=2.4.6 <2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch", - ">=2.4.7 <2.4.8": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch" + ">=2.4.4 <2.4.4-p10": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch", + ">=2.4.5 <2.4.5-p9": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch", + ">=2.4.6 <2.4.6-p7": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch", + ">=2.4.7 <2.4.7-p2": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch" } }, "magento/module-paypal": { From 4d997b25274100d6c19a8ade2d6b2288b70ee6ee Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Thu, 24 Oct 2024 16:03:13 +0530 Subject: [PATCH 33/82] MAGECLOUD-12969: security fix patch --- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch index 53317da..5175edb 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch @@ -59,4 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); From e566326ae50af80ea3eebbc6ce90e5454a165148 Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Thu, 24 Oct 2024 20:07:37 +0530 Subject: [PATCH 34/82] MAGECLOUD-12969: security fix patch --- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch index 3ea35c2..e5740c2 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch @@ -59,4 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch index 1b7d545..1296cc0 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch @@ -43,4 +43,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch index cf9af8b..4f23fb7 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch @@ -52,4 +52,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); From 337afefa93bfe97471a6b51495ee6252f3a6242f Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Thu, 24 Oct 2024 20:10:01 +0530 Subject: [PATCH 35/82] MAGECLOUD-12969: security fix patch --- ...LOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch | 1 + ...LOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch | 1 + ...LOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch | 1 + ...LOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch | 1 + 4 files changed, 4 insertions(+) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch index 5175edb..6fbe148 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch @@ -60,3 +60,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p //Making sure keys are large enough. foreach ($this->keys as &$key) { $key = str_pad($key, 2048, '&', STR_PAD_BOTH); + diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch index e5740c2..40c807a 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch @@ -60,3 +60,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p //Making sure keys are large enough. foreach ($this->keys as &$key) { $key = str_pad($key, 2048, '&', STR_PAD_BOTH); + diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch index 1296cc0..43c5911 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch @@ -44,3 +44,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p //Making sure keys are large enough. foreach ($this->keys as &$key) { $key = str_pad($key, 2048, '&', STR_PAD_BOTH); + diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch index 4f23fb7..fd9df08 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch @@ -53,3 +53,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p //Making sure keys are large enough. foreach ($this->keys as &$key) { $key = str_pad($key, 2048, '&', STR_PAD_BOTH); + From 2d92cab692044fd9d321167352f367b62fe54b0c Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Thu, 24 Oct 2024 20:17:14 +0530 Subject: [PATCH 36/82] MAGECLOUD-12969: security fix patch --- ...UD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch | 3 +-- ...UD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch | 3 +-- ...UD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch | 3 +-- ...UD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch index 6fbe148..53317da 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch @@ -59,5 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); - + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch index 40c807a..3ea35c2 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch @@ -59,5 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); - + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch index 43c5911..1b7d545 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch @@ -43,5 +43,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); - + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch index fd9df08..cf9af8b 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch @@ -52,5 +52,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); - + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file From c6a4b6d6eaa80115b70d39789452321206464681 Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Thu, 24 Oct 2024 20:41:15 +0530 Subject: [PATCH 37/82] MAGECLOUD-12969: security fix patch --- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch | 2 +- ...OUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch index 53317da..5175edb 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch @@ -59,4 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch index 3ea35c2..e5740c2 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch @@ -59,4 +59,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch index 1b7d545..1296cc0 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch @@ -43,4 +43,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch index cf9af8b..4f23fb7 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch @@ -52,4 +52,4 @@ diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.p + $this->keys = [end($this->keys)]; //Making sure keys are large enough. foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); \ No newline at end of file + $key = str_pad($key, 2048, '&', STR_PAD_BOTH); From 4e4f21ea1f53032b03c3ca58e25895fed7746cfc Mon Sep 17 00:00:00 2001 From: "Pawan.kumar" Date: Fri, 25 Oct 2024 13:56:45 +0530 Subject: [PATCH 38/82] MAGECLOUD-12969: security fix patch --- patches.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patches.json b/patches.json index 50ad2cb..8f41963 100644 --- a/patches.json +++ b/patches.json @@ -282,10 +282,10 @@ ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" }, "Patch for CVE-2024-34102 - CosmicSting": { - ">=2.4.4 <2.4.4-p10": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch", - ">=2.4.5 <2.4.5-p9": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", - ">=2.4.6 <2.4.6-p7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", - ">=2.4.7 <2.4.7-p1": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" + ">=2.4.4 <2.4.4-p8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch", + ">=2.4.5 <2.4.5-p7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", + ">=2.4.6 <2.4.6-p5": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", + "2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" }, "Patch for CVE-2024-34102 - KeyRotation": { ">=2.4.4 <2.4.4-p10": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch", From a5a58ac237c4a622e5ca096c7026cc3c89915ea5 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Mon, 28 Oct 2024 09:41:07 -0500 Subject: [PATCH 39/82] add new line --- ...UD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch | 3 ++- ...UD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch | 3 ++- ...UD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch index 83b15a8..21ca631 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch @@ -160,4 +160,5 @@ index b4e471f4e40ef..495234759a7f8 100644 + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch index 26c492e..13cec8e 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch @@ -159,4 +159,5 @@ index b4e471f4e40ef..495234759a7f8 100644 + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch index 4f2178b..c4f7c72 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch @@ -156,4 +156,5 @@ index b4e471f4e40ef..495234759a7f8 100644 + + + - \ No newline at end of file + + \ No newline at end of file From 13e7121ceaa515799089d16992664666f8106f66 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Mon, 28 Oct 2024 10:00:43 -0500 Subject: [PATCH 40/82] remove trailing space --- ...LOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch | 1 - ...LOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch | 1 - ...LOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch | 1 - 3 files changed, 3 deletions(-) diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch index 21ca631..94adc61 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch @@ -161,4 +161,3 @@ index b4e471f4e40ef..495234759a7f8 100644 + + - \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch index 13cec8e..29adfc3 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch @@ -160,4 +160,3 @@ index b4e471f4e40ef..495234759a7f8 100644 + + - \ No newline at end of file diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch index c4f7c72..c5ba438 100644 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch +++ b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch @@ -157,4 +157,3 @@ index b4e471f4e40ef..495234759a7f8 100644 + + - \ No newline at end of file From 067fddce4f560fd08255f59f8b7265952a8fbb27 Mon Sep 17 00:00:00 2001 From: Sivaram Manijeganathan Date: Thu, 31 Oct 2024 10:31:48 -0500 Subject: [PATCH 41/82] update version for release 1.1.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 636d694..5d90152 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.0.27", + "version": "1.1.2", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From f435469bd4fe8034eee7c9096a1ab2feb3f33420 Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Wed, 20 Nov 2024 20:55:27 +0530 Subject: [PATCH 42/82] MCLOUD-11623: Resolved merge conflicts --- patches.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/patches.json b/patches.json index e421f82..ea81ffb 100644 --- a/patches.json +++ b/patches.json @@ -370,9 +370,6 @@ "magento/magento2-b2b-base": { "Layered navigation filter is present only when product is present on the listing page with enabled Shared catalog": { ">=1.1.5 <1.3.1": "MCLOUD-6923__layered_navigation_filter_is_present_only_when_product_is_present_on_the_listing_page_with_enabled_shared_catalog__2.3.5.patch" - }, - "Fixes the issue where the file generated after Requisition List export is not removed from the var/ directory": { - ">=1.3.1 <1.3.6": "MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch" } }, "magento/magento2-ee-base": { From 528d4dff0d7005908c88959ffe3789a7d25a9a48 Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Wed, 20 Nov 2024 20:56:48 +0530 Subject: [PATCH 43/82] MCLOUD-11623: Resolved merge conflicts & re-added patch change which was reverted due to conflict --- patches.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/patches.json b/patches.json index 8f41963..898e74d 100644 --- a/patches.json +++ b/patches.json @@ -385,6 +385,9 @@ }, "Fields hydration on company account create request": { ">=1.3.3 <1.3.3-p11 || >=1.3.4 <1.3.4-p10 || >=1.3.5 <1.3.5-p8 || >=1.4.2 <1.4.2-p3": "B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch" + }, + "Fixes the issue where the file generated after Requisition List export is not removed from the var/ directory": { + ">=1.3.1 <1.3.6": "MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch" } }, "magento/magento2-ee-base": { From 4b29c1dca3fa938f36f84b6e235de45a8de686e3 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Thu, 9 Jan 2025 13:47:00 +0530 Subject: [PATCH 44/82] MCLOUD-13149: Add support of php 8.4 to cloud-patches --- composer.json | 16 ++++++++-------- src/App/GenericException.php | 2 +- src/Console/QuestionFactory.php | 2 +- src/Patch/Data/Patch.php | 8 ++++++++ src/Shell/ProcessFactory.php | 2 +- .../Command/Process/Action/RevertActionTest.php | 7 +++++++ src/Test/Unit/Shell/Command/PatchDriverTest.php | 5 +++-- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 5d90152..e128d49 100644 --- a/composer.json +++ b/composer.json @@ -13,15 +13,15 @@ "require": { "php": "^8.0", "ext-json": "*", - "composer/composer": "^1.9 || ^2.0", + "composer/composer": "^1.9 || ^2.8 || !=2.2.16", "composer/semver": "@stable", - "monolog/monolog": "^1.25||^2.3||^2.7|| ^3.6", - "symfony/config": "^3.3||^4.4||^5.0||^6.0", - "symfony/console": "^2.8 || ^4.0 || ^5.1 || ^5.4 || ^6.4", - "symfony/dependency-injection": "^3.3||^4.3||^5.0||^6.0", - "symfony/process": "^2.1 || ^4.1 || ^5.1 || ^5.4 || ^6.4", + "monolog/monolog": "^2.3 || ^2.7 || ^3.6", + "symfony/config": "^4.4 || ^5.1 || ^5.4 || ^6.4", + "symfony/console": "^4.4 || ^5.1 || ^5.4 || ^6.4", + "symfony/dependency-injection": "^4.4 || ^5.1 || ^5.4 || ^6.4", + "symfony/process": "^4.4 || ^5.1 || ^5.4 || ^6.4", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", - "symfony/yaml": "^3.3||^4.0||^5.0||^6.0||^7.0", + "symfony/yaml": "^4.4 || ^5.1 || ^5.4 || ^6.4", "magento/quality-patches": "^1.1.0" }, "require-dev": { @@ -32,7 +32,7 @@ "codeception/module-rest": "^1.2 || ^3.0", "consolidation/robo": "^1.2 || ^3.0 || ^5.0", "phpmd/phpmd": "@stable", - "phpunit/phpunit": "^8.5 || ^9.5", + "phpunit/phpunit": "^9.5.10 || ^10", "squizlabs/php_codesniffer": "^3.0" }, "bin": [ diff --git a/src/App/GenericException.php b/src/App/GenericException.php index 06955e8..f7b1eea 100644 --- a/src/App/GenericException.php +++ b/src/App/GenericException.php @@ -19,7 +19,7 @@ class GenericException extends \Exception * @param int $code * @param Throwable|null $previous */ - public function __construct(string $message, int $code = 0, Throwable $previous = null) + public function __construct(string $message ="", int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Console/QuestionFactory.php b/src/Console/QuestionFactory.php index 9a21dfd..d45f741 100644 --- a/src/Console/QuestionFactory.php +++ b/src/Console/QuestionFactory.php @@ -22,7 +22,7 @@ class QuestionFactory * * @return Question */ - public function create(string $question, string $default = null): Question + public function create(string $question, ?string $default = null): Question { return new Question($question, $default); } diff --git a/src/Patch/Data/Patch.php b/src/Patch/Data/Patch.php index 5e1deb4..02b390e 100644 --- a/src/Patch/Data/Patch.php +++ b/src/Patch/Data/Patch.php @@ -148,6 +148,14 @@ public function getId(): string return $this->id; } + /** + * @inheritDoc + */ + public function setId(): string + { + return $this->id; + } + /** * @inheritDoc */ diff --git a/src/Shell/ProcessFactory.php b/src/Shell/ProcessFactory.php index 5184bea..4cb7a3f 100644 --- a/src/Shell/ProcessFactory.php +++ b/src/Shell/ProcessFactory.php @@ -49,7 +49,7 @@ public function __construct(DirectoryList $directoryList, Composer $composer) * @return Process * @throws PackageNotFoundException */ - public function create(array $cmd, string $input = null): Process + public function create(array $cmd, ?string $input = null): Process { return new Process( $this->processSupportsArrayParam() ? $cmd : implode(' ', $cmd), diff --git a/src/Test/Unit/Command/Process/Action/RevertActionTest.php b/src/Test/Unit/Command/Process/Action/RevertActionTest.php index b8f515e..206f673 100644 --- a/src/Test/Unit/Command/Process/Action/RevertActionTest.php +++ b/src/Test/Unit/Command/Process/Action/RevertActionTest.php @@ -56,12 +56,19 @@ class RevertActionTest extends TestCase * @var RevertValidator|MockObject */ private $revertValidator; + + /** + * @var revertAction|MockObject + */ + protected $revertAction; /** * @inheritdoc */ protected function setUp(): void { + // Initialize the $revertAction property + $this->revertAction = $this->createMock(RevertAction::class); $this->applier = $this->createMock(Applier::class); $this->revertValidator = $this->createMock(RevertValidator::class); $this->statusPool = $this->createMock(StatusPool::class); diff --git a/src/Test/Unit/Shell/Command/PatchDriverTest.php b/src/Test/Unit/Shell/Command/PatchDriverTest.php index 02ac4ae..b1a4b4d 100644 --- a/src/Test/Unit/Shell/Command/PatchDriverTest.php +++ b/src/Test/Unit/Shell/Command/PatchDriverTest.php @@ -36,12 +36,13 @@ class PatchDriverTest extends TestCase */ protected function setUp(): void { + parent::setUp(); $this->baseDir = dirname(__DIR__, 5) . '/tests/unit/'; $this->cwd = $this->baseDir . 'var/'; $processFactory = $this->createMock(ProcessFactory::class); $processFactory->method('create') ->willReturnCallback( - function (array $cmd, string $input = null) { + function (array $cmd, ?string $input = null) { return new Process( $cmd, $this->cwd, @@ -179,7 +180,7 @@ private function getFileContent(string $path): string * @param string $path * @param string|null $name */ - private function copyFileToWorkingDir(string $path, string $name = null) + private function copyFileToWorkingDir(string $path, ?string $name = null) { $name = $name ?? basename($path); copy($path, $this->getVarFile($name)); From d48d63dd444c973c8b1c2cdc76abda8a0684415c Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 14 Jan 2025 20:52:23 +0530 Subject: [PATCH 45/82] Add support of php 8.4 to cloud-patches --- src/Patch/Data/Patch.php | 8 -- .../Action/ApplyOptionalActionTest.php | 94 ++++++++----- .../Action/ConfirmRequiredActionTest.php | 38 +++++- .../Action/ProcessDeprecatedActionTest.php | 71 ++++++++-- .../Process/Action/RevertActionTest.php | 69 +++++++--- .../Action/ReviewAppliedActionTest.php | 7 +- .../Unit/Command/Process/ApplyLocalTest.php | 30 ++++- .../Command/Process/ApplyOptionalTest.php | 8 +- .../Command/Process/ApplyRequiredTest.php | 46 +++++-- .../Command/Process/Ece/ApplyOptionalTest.php | 9 +- .../Unit/Command/Process/Ece/RevertTest.php | 37 ++++-- .../Unit/Command/Process/RendererTest.php | 7 +- src/Test/Unit/Command/Process/RevertTest.php | 25 +++- .../Unit/Command/Process/ShowStatusTest.php | 27 +++- src/Test/Unit/Patch/AggregatorTest.php | 17 ++- .../Patch/Collector/CloudCollectorTest.php | 95 +++++++++----- .../Patch/Collector/LocalCollectorTest.php | 51 ++++++-- .../Patch/Collector/QualityCollectorTest.php | 123 +++++++++++++----- .../Unit/Patch/Conflict/ApplyCheckerTest.php | 7 +- .../Unit/Patch/Conflict/ProcessorTest.php | 29 ++++- 20 files changed, 591 insertions(+), 207 deletions(-) diff --git a/src/Patch/Data/Patch.php b/src/Patch/Data/Patch.php index 02b390e..5e1deb4 100644 --- a/src/Patch/Data/Patch.php +++ b/src/Patch/Data/Patch.php @@ -148,14 +148,6 @@ public function getId(): string return $this->id; } - /** - * @inheritDoc - */ - public function setId(): string - { - return $this->id; - } - /** * @inheritDoc */ diff --git a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php index eb0c62f..2801521 100644 --- a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php @@ -108,7 +108,12 @@ public function testExecuteSuccessful() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patch1, $patch2, $patch3]); $this->applier->method('apply') @@ -117,18 +122,29 @@ public function testExecuteSuccessful() [$patch2->getPath(), $patch2->getId(), 'Patch ' . $patch2->getId() .' has been applied'], [$patch3->getPath(), $patch3->getId(), 'Patch ' . $patch3->getId() .' has been applied'], ]); - $this->renderer->expects($this->exactly(3)) - ->method('printPatchInfo') - ->withConsecutive( - [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied'], - [$outputMock, $patch2, 'Patch ' . $patch2->getId() .' has been applied'], - [$outputMock, $patch3, 'Patch ' . $patch3->getId() .' has been applied'] - ); - + ->method('printPatchInfo') + ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + static $callCount = 0; + $expectedPatches = [$patch1, $patch2, $patch3]; + $expectedMessages = [ + 'Patch ' . $patch1->getId() . ' has been applied', + 'Patch ' . $patch2->getId() . ' has been applied', + 'Patch ' . $patch3->getId() . ' has been applied' + ]; + + if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { + $callCount++; + return true; + } + + return false; + }); $this->action->execute($inputMock, $outputMock, $patchFilter); } + + /** * Tests successful optional patches applying. * @@ -149,9 +165,13 @@ public function testApplyAlreadyAppliedPatch() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patch1]); - $this->applier->expects($this->never()) ->method('apply'); $this->renderer->expects($this->never()) @@ -159,17 +179,17 @@ public function testApplyAlreadyAppliedPatch() $outputMock->expects($this->once()) ->method('writeln') - ->withConsecutive( - [ - $this->stringContains( - 'Patch ' . $patch1->getId() .' (' . $patch1->getFilename() . ') was already applied' - ) - ] + ->with( + + $this->stringContains( + 'Patch ' . $patch1->getId() .' (' . $patch1->getFilename() . ') was already applied' + ) ); $this->action->execute($inputMock, $outputMock, $patchFilter); } + /** * Tests successful optional patches applying. * @@ -203,8 +223,8 @@ public function testApplyingAllPatchesAndSkipDeprecated() $this->renderer->expects($this->once()) ->method('printPatchInfo') - ->withConsecutive( - [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied'] + ->with( + $outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied' ); $this->action->execute($inputMock, $outputMock, $patchFilter); @@ -232,27 +252,35 @@ public function testApplyWithException() ->willReturn([$patch1, $patch2]); $this->applier->method('apply') - ->willReturnMap([ - [$patch1->getPath(), $patch1->getId()], - [$patch2->getPath(), $patch2->getId()] - ])->willReturnCallback( - function ($path, $id) { - if ($id === 'MC-22222') { - throw new ApplierException('Applier error message'); - } - - return "Patch {$path} {$id} has been applied"; + ->willReturnCallback(function ($path, $id) use ($patch1, $patch2) { + if ($id === 'MC-22222') { + throw new ApplierException('Applier error message'); } - ); - + // Return success message for the first patch + return "Patch {$path} {$id} has been applied"; + }); $this->conflictProcessor->expects($this->once()) ->method('process') - ->withConsecutive([$outputMock, $patch2, [$patch1], 'Applier error message']) + ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + static $callCount = 0; + $expectedPatches = [$patch1, $patch2, $patch3]; + $expectedMessages = [ + 'Patch ' . $patch1->getId() . ' has been applied', + 'Patch ' . $patch2->getId() . ' has been applied', + 'Patch ' . $patch3->getId() . ' has been applied' + ]; + + if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { + $callCount++; + return true; + } + + return false; + }) ->willThrowException(new RuntimeException('Error message')); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); - $this->action->execute($inputMock, $outputMock, $patchFilter); } diff --git a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php index 2d374ca..f71b022 100644 --- a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php @@ -91,7 +91,12 @@ public function testAskConfirmationForNotAppliedPatches() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patch1, $patch2, $patch3]); $aggregatedPatch = $this->getMockForAbstractClass(AggregatedPatchInterface::class); @@ -102,8 +107,13 @@ public function testAskConfirmationForNotAppliedPatches() $this->renderer->expects($this->once()) ->method('printTable') - ->withConsecutive([$outputMock, [$aggregatedPatch]]); - + ->with($outputMock, [$aggregatedPatch]) + ->willReturnCallback(function($output,$patches ) use ($outputMock, $aggregatedPatch) { + if ($output === $outputMock && $aggregatedPatch === [$aggregatedPatch] ) { + throw new RuntimeException('Error message'); + } + return null; + }); $this->renderer->expects($this->once()) ->method('printQuestion') ->willReturn(true); @@ -124,7 +134,12 @@ public function testPatchNotFoundException() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willThrowException(new PatchNotFoundException('')); $this->expectException(RuntimeException::class); @@ -149,7 +164,12 @@ public function testConfirmationRejected() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patch1]); $aggregatedPatch = $this->getMockForAbstractClass(AggregatedPatchInterface::class); @@ -160,7 +180,13 @@ public function testConfirmationRejected() $this->renderer->expects($this->once()) ->method('printTable') - ->withConsecutive([$outputMock, [$aggregatedPatch]]); + ->with($outputMock, [$aggregatedPatch]) + ->willReturnCallback(function($output,$patches ) use ($outputMock, $aggregatedPatch) { + if ($output === $outputMock && $aggregatedPatch === [$aggregatedPatch] ) { + throw new RuntimeException('Error message'); + } + return null; + }); $this->renderer->expects($this->once()) ->method('printQuestion') diff --git a/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php b/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php index 441a072..717335f 100644 --- a/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php @@ -105,11 +105,21 @@ public function testProcessDeprecationSuccessful() $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patchMock]); $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->withConsecutive([$patch1->getId()]) + ->willReturnCallback(function($patchId) use ($patchFilter, $patch1) { + if ($patchId === $patch1->getId()) { + return [$patch1]; + } + return []; + }) ->willReturn([]); $this->aggregator->expects($this->once()) @@ -118,8 +128,12 @@ public function testProcessDeprecationSuccessful() $outputMock->expects($this->once()) ->method('writeln') - ->withConsecutive([$this->stringContains($expectedMessage)]); - + ->willReturnCallback(function($patchId) use ($patchFilter) { + if ($patchId === $expectedMessage) { + $this->stringContains($expectedMessage); + } + return []; + }); $this->renderer->expects($this->once()) ->method('printQuestion') ->willReturn(true); @@ -143,7 +157,12 @@ public function testProcessDeprecationException() $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -185,7 +204,12 @@ public function testProcessReplacementSuccessful() $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -194,12 +218,22 @@ public function testProcessReplacementSuccessful() $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->withConsecutive([$patch1->getId()]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn($requireReplacement); $outputMock->expects($this->once()) ->method('writeln') - ->withConsecutive([$this->stringContains($expectedMessage)]); + ->willReturnCallback(function($patchId) use ($patchFilter) { + if ($patchId === $expectedMessage) { + $this->stringContains($expectedMessage); + } + return []; + }); $this->renderer->expects($this->once()) ->method('printQuestion') @@ -229,7 +263,12 @@ public function testSkippingReplacementProcessForAppliedPatch() $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -264,7 +303,12 @@ public function testProcessReplacementException() $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -273,7 +317,12 @@ public function testProcessReplacementException() $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->withConsecutive([$patch1->getId()]) + ->willReturnCallback(function($patchId) use ($patchFilter, $patch1) { + if ($patchId === $patch1->getId()) { + return [$patch1]; + } + return []; + }) ->willReturn($requireReplacement); $this->renderer->expects($this->once()) diff --git a/src/Test/Unit/Command/Process/Action/RevertActionTest.php b/src/Test/Unit/Command/Process/Action/RevertActionTest.php index 206f673..0c722e1 100644 --- a/src/Test/Unit/Command/Process/Action/RevertActionTest.php +++ b/src/Test/Unit/Command/Process/Action/RevertActionTest.php @@ -110,7 +110,12 @@ public function testExecuteSuccessful() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter, false]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patch1, $patch2]); $this->applier->method('revert') @@ -121,10 +126,21 @@ public function testExecuteSuccessful() $this->renderer->expects($this->exactly(2)) ->method('printPatchInfo') - ->withConsecutive( - [$outputMock, $patch2, 'Patch ' . $patch2->getId() .' has been reverted'], - [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been reverted'] - ); + ->willReturnCallback(function() use ($patch1, $patch2) { + static $callCount = 0; + $expectedPatches = [$patch1, $patch2]; + $expectedMessages = [ + 'Patch ' . $patch1->getId() . ' has been applied', + 'Patch ' . $patch2->getId() . ' has been applied' + ]; + + if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { + $callCount++; + return true; + } + + return false; + }); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -149,7 +165,12 @@ public function testRevertNotAppliedPatch() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willReturn([$patch1]); $this->applier->expects($this->never()) @@ -159,14 +180,14 @@ public function testRevertNotAppliedPatch() $outputMock->expects($this->once()) ->method('writeln') - ->withConsecutive( - [ + ->willReturnCallback(function($patchId) use ($patchFilter,$patch1) { + if ($patchId === $expectedMessage && $$patch1 === $patch1->getId()) { $this->stringContains( 'Patch ' . $patch1->getId() . ' (' . $patch1->getFilename() . ') is not applied' - ) - ] - ); - + ); + } + return []; + }); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -193,10 +214,12 @@ public function testRevertWithException() $outputMock->expects($this->once()) ->method('writeln') - ->withConsecutive( - [$this->stringContains($errorMessage)] - ); - + ->willReturnCallback(function($patchId) use ($patchFilter) { + if ($patchId === $errorMessage) { + $this->stringContains($errorMessage); + } + return []; + }); $this->expectException(RuntimeException::class); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -214,7 +237,12 @@ public function testPatchNotFoundException() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willThrowException(new PatchNotFoundException('')); $this->expectException(RuntimeException::class); @@ -235,7 +263,12 @@ public function testValidationFailedException() $this->revertValidator->expects($this->once()) ->method('validate') - ->withConsecutive([$patchFilter]) + ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } + return []; + }) ->willThrowException(new RuntimeException('Error')); $this->optionalPool->expects($this->never()) ->method('getList'); diff --git a/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php b/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php index b793c37..b933550 100644 --- a/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php @@ -95,7 +95,12 @@ public function testAppliedPatchesExceedsLimit() $outputMock->expects($this->once()) ->method('writeln') - ->withConsecutive([$this->stringContains('error')]); + ->willReturnCallback(function($filter) use ($patch1) { + if ($filter === $patch1) { + $this->stringContains('error'); + } + return false; + }); $this->action->execute($inputMock, $outputMock, $patchFilter); } diff --git a/src/Test/Unit/Command/Process/ApplyLocalTest.php b/src/Test/Unit/Command/Process/ApplyLocalTest.php index d221269..4c3d50d 100644 --- a/src/Test/Unit/Command/Process/ApplyLocalTest.php +++ b/src/Test/Unit/Command/Process/ApplyLocalTest.php @@ -125,12 +125,23 @@ public function testApplySuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->withConsecutive( - [$this->anything()], - [$this->stringContains('Patch ' . $patch1->getTitle() .' has been applied')], - [$this->stringContains('Patch ' . $patch2->getTitle() .' has been applied')], - [$this->stringContains('Patch ' . $patch3->getTitle() .' has been applied')] - ); + ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + static $callCount = 0; + $expectedPatches = [$patch1, $patch2, $patch3]; + $expectedMessages = [ + $this->anything(), + 'Patch ' . $patch1->getTitle() . ' has been applied', + 'Patch ' . $patch2->getTitle() . ' has been applied', + 'Patch ' . $patch3->getTitle() . ' has been applied' + ]; + + if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { + $callCount++; + return true; + } + + return false; + }); $this->manager->run($inputMock, $outputMock); } @@ -169,7 +180,12 @@ function ($path, $title) { $this->rollbackProcessor->expects($this->once()) ->method('process') - ->withConsecutive([[$patch1]]) + ->willReturnCallback(function($filter) use ($patch1) { + if ($filter === $patch1) { + return [$patch1]; + } + return []; + }) ->willReturn($rollbackMessages); $this->expectException(RuntimeException::class); diff --git a/src/Test/Unit/Command/Process/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/ApplyOptionalTest.php index 0b94730..841d923 100644 --- a/src/Test/Unit/Command/Process/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/ApplyOptionalTest.php @@ -82,8 +82,12 @@ public function testApplyWithPatchArgumentProvided() $this->actionPool->expects($this->once()) ->method('execute') - ->withConsecutive([$inputMock, $outputMock, $cliPatchArgument]); - + ->willReturnCallback(function($input, $output, $cliPatch) use ($inputputMock, $outputMock, $patch) { + if ($input === $inputputMock && $output === $outputMock && $patches === $cliPatch) { + return true; + } + return false; + }); $this->applyOptional->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index 45b794a..d158949 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -103,12 +103,22 @@ public function testApplySuccessful() $this->renderer->expects($this->exactly(3)) ->method('printPatchInfo') - ->withConsecutive( - [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied'], - [$outputMock, $patch2, 'Patch ' . $patch2->getId() .' has been applied'], - [$outputMock, $patch3, 'Patch ' . $patch3->getId() .' has been applied'] - ); - + ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + static $callCount = 0; + $expectedPatches = [$patch1, $patch2, $patch3]; + $expectedMessages = [ + 'Patch ' . $patch1->getId() . ' has been applied', + 'Patch ' . $patch2->getId() . ' has been applied', + 'Patch ' . $patch3->getId() . ' has been applied' + ]; + + if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { + $callCount++; + return true; + } + + return false; + }); $this->manager->run($inputMock, $outputMock); } @@ -129,17 +139,29 @@ public function testApplyWithException() ->willReturn([$patch]); $this->applier->method('apply') - ->withConsecutive([$patch->getPath(), $patch->getId()]) + ->willReturnCallback(function ($args) { + static $series = [ + $patch->getPath(), + $patch->getId() + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }) ->willThrowException(new ApplierException('Applier error message')); - $this->conflictProcessor->expects($this->once()) - ->method('process') - ->withConsecutive([$outputMock, $patch, [], 'Applier error message']) - ->willThrowException(new RuntimeException('Error message')); + $this->conflictProcessor->expects($this->once()) + ->method('process') + ->with($outputMock, $patch, [], 'Applier error message') + ->willReturnCallback(function($output, $patch, $data = '', string $errorMessage) use ($outputMock, $patch2, $patch1) { + if ($output === $outputMock && $patch === $patch2 && $data === $patch1 && $errorMessage === 'Applier error message') { + throw new RuntimeException('Error message'); + } + return null; + }); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); - $this->manager->run($inputMock, $outputMock); + $this->manager->run($inputMock, $outputMock, $patchFilter); } /** diff --git a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php index 4f547b8..9a4b881 100644 --- a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php @@ -88,8 +88,13 @@ public function testApplyWithPatchEnvVariableProvided() $this->actionPool->expects($this->once()) ->method('execute') - ->withConsecutive([$inputMock, $outputMock, $configQualityPatches]); - + ->with($inputMock, $outputMock, $configQualityPatches) + ->willReturnCallback(function($input, $output, $config) use ($inputMock, $outputMock, $configQualityPatches) { + if ($input === $inputMock && $output === $outputMock && $config === $configQualityPatches) { + return true; + } + return null; + }); $this->applyOptionalEce->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Command/Process/Ece/RevertTest.php b/src/Test/Unit/Command/Process/Ece/RevertTest.php index cbba07a..c9c539b 100644 --- a/src/Test/Unit/Command/Process/Ece/RevertTest.php +++ b/src/Test/Unit/Command/Process/Ece/RevertTest.php @@ -116,16 +116,31 @@ public function testRevertSuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->withConsecutive( - [$this->anything()], - [$this->stringContains('Patch ' . $patch2->getTitle() .' has been reverted')], - [$this->stringContains('Patch ' . $patch1->getTitle() .' has been reverted')] - ); + ->willReturnCallback(function() use ($patch1, $patch2) { + static $callCount = 0; + $expectedPatches = [$patch1, $patch2, $patch3]; + $expectedMessages = [ + $this->anything(), + 'Patch ' . $patch1->getTitle() . ' has been reverted', + 'Patch ' . $patch2->getTitle() . ' has been reverted', + ]; + + if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { + $callCount++; + return true; + } + return false; + }); $this->revertAction->expects($this->once()) ->method('execute') - ->withConsecutive([$inputMock, $outputMock, []]); - + ->with($inputMock, $outputMock, []) + ->willReturnCallback(function($input, $output) use ($inputMock, $outputMock) { + if ($output === $outputMock && $input === $inputMock && $patch === [] ) { + return true; + } + return false; + }); $this->revertEce->run($inputMock, $outputMock); } @@ -167,7 +182,13 @@ function ($path, $title) { $this->revertAction->expects($this->once()) ->method('execute') - ->withConsecutive([$inputMock, $outputMock, []]); + ->with($inputMock, $outputMock, []) + ->willReturnCallback(function($input, $output) use ($inputMock, $outputMock) { + if ($output === $outputMock && $input === $inputMock && $patch === [] ) { + return true; + } + return false; + }); $this->revertEce->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Command/Process/RendererTest.php b/src/Test/Unit/Command/Process/RendererTest.php index b7b1f66..facf535 100644 --- a/src/Test/Unit/Command/Process/RendererTest.php +++ b/src/Test/Unit/Command/Process/RendererTest.php @@ -76,7 +76,12 @@ public function testPrintPatchInfo(PatchInterface $patch, string $prependedMessa $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $outputMock->expects($this->atLeastOnce()) ->method('writeln') - ->withConsecutive([$expectedArray]); + ->willReturnCallback(function($filter) use ($expectedArray, $patch1) { + if ($filter === $expectedArray) { + return [$patch1]; + } + return []; + }); $this->renderer->printPatchInfo($outputMock, $patch, $prependedMessage); } diff --git a/src/Test/Unit/Command/Process/RevertTest.php b/src/Test/Unit/Command/Process/RevertTest.php index d2d00a4..793c397 100644 --- a/src/Test/Unit/Command/Process/RevertTest.php +++ b/src/Test/Unit/Command/Process/RevertTest.php @@ -83,13 +83,24 @@ public function testRevertWithPatchArgumentProvided() ->with(RevertCommand::OPT_ALL) ->willReturn($cliOptAll); $this->filterFactory->method('createRevertFilter') - ->withConsecutive([$cliOptAll, $cliPatchArgument]) + ->with($cliOptAll, $cliPatchArgument) + ->willReturnCallback(function($patches) use ($cliOptAll, $cliPatchArgument) { + if ($patches === $cliOptAll && $patches === $cliPatchArgument) { + return true; + } + return false; + }) ->willReturn($cliPatchArgument); $this->revertAction->expects($this->once()) ->method('execute') - ->withConsecutive([$inputMock, $outputMock, $cliPatchArgument]); - + ->with($inputMock, $outputMock, $cliPatchArgument) + ->willReturnCallback(function($input, $output, $cliPatch) use ($inputMock, $outputMock, $cliPatchArgument) { + if ($input === $inputMock && $output === $outputMock && $cliPatch === $cliPatchArgument) { + return true; + } + return false; + }); $this->manager->run($inputMock, $outputMock); } @@ -117,7 +128,13 @@ public function testRevertWithEmptyPatchArgument() ->with(RevertCommand::OPT_ALL) ->willReturn($cliOptAll); $this->filterFactory->method('createRevertFilter') - ->withConsecutive([$cliOptAll, $cliPatchArgument]) + ->with($cliOptAll, $cliPatchArgument) + ->willReturnCallback(function($cliOpt, $cliPatch) use ($cliOptAll, $cliPatchArgument) { + if ($cliOpt === $cliOptAll && $cliPatch === $cliPatchArgument) { + return true; + } + return false; + }) ->willReturn(null); $this->revertAction->expects($this->never()) diff --git a/src/Test/Unit/Command/Process/ShowStatusTest.php b/src/Test/Unit/Command/Process/ShowStatusTest.php index 6caf965..b0e2a55 100644 --- a/src/Test/Unit/Command/Process/ShowStatusTest.php +++ b/src/Test/Unit/Command/Process/ShowStatusTest.php @@ -140,7 +140,12 @@ public function testShowStatus() $this->reviewAppliedAction->expects($this->once()) ->method('execute') - ->withConsecutive([$inputMock, $outputMock, []]); + ->willReturnCallback(function($input, $output, $patches) use ($inputMock, $outputMock) { + if ($input === $outputMock && $output === $outputMock ) { + return true; + } + return false; + }); $this->optionalPool->method('getList') ->willReturn([$patchMock]); $this->localPool->method('getList') @@ -153,16 +158,24 @@ public function testShowStatus() // Show warning message about patch deprecation $outputMock->expects($this->exactly(4)) ->method('writeln') - ->withConsecutive( - [$this->anything()], - [$this->stringContains('Deprecated patch ' . $patch1->getId() . ' is currently applied')] - ); + ->willReturnCallback(function($filter) use ($patch1) { + if ($filter === $patch1->getId()) { + $this->anything(); + $this->stringContains('Deprecated patch ' . $patch1->getId() . ' is currently applied'); + } + return false; + }); // Show patches in the table $this->renderer->expects($this->once()) ->method('printTable') - ->withConsecutive([$outputMock, [$patch1, $patch2, $patch5]]); - + ->with($outputMock, [$patch1, $patch2, $patch5]) + ->willReturnCallback(function($output, $patches) use ($outputMock, $patch, $patch2, $patch5) { + if ($output === $outputMock && $patches === [$patch2] && $patches === [$patch2] && $patches === [$patch2]) { + return true; + } + return false; + }); $this->manager->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Patch/AggregatorTest.php b/src/Test/Unit/Patch/AggregatorTest.php index b438c51..0348396 100644 --- a/src/Test/Unit/Patch/AggregatorTest.php +++ b/src/Test/Unit/Patch/AggregatorTest.php @@ -50,12 +50,17 @@ public function testAggregate() $patch3 = $this->createPatch('MC-3', 'Patch3'); $this->aggregatedPatchFactory->expects($this->exactly(3)) - ->method('create') - ->withConsecutive( - [[$patch1CE, $patch1EE, $patch1B2B]], - [[$patch2CE, $patch2EE]], - [[$patch3]] - ); + ->method('create') + ->willReturnCallback(function () use (&$callCount) { + $callCount++; + if ($callCount === 1) { + return [$patch1CE, $patch1EE, $patch1B2B]; + } elseif ($callCount === 2) { + return [$patch2CE, $patch2EE]; + } elseif($callCount === 3){ + return [$patch3]; + } + }); $this->assertTrue( is_array( diff --git a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php index b0a3ff2..8197ea1 100644 --- a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php @@ -105,49 +105,82 @@ public function testCollectSuccessful(bool $isCloud, string $expectedType) $this->patchBuilder->expects($this->exactly(3)) ->method('setId') - ->withConsecutive(['MDVA-2470'], ['MDVA-2470'], ['MAGECLOUD-2033']); + ->willReturnCallback(function ($args) { + static $series = [ + 'MDVA-2470', 'MDVA-2470', 'MAGECLOUD-2033' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setTitle') - ->withConsecutive( - ['Fix asset locker race condition when using Redis'], - ['Fix asset locker race condition when using Redis EE'], - ['Allow DB dumps done with the support module to complete'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + 'Fix asset locker race condition when using Redis', + 'Fix asset locker race condition when using Redis EE', + 'Allow DB dumps done with the support module to complete' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setFilename') - ->withConsecutive( - ['MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], - ['MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], - ['MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + 'MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', + 'MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', + 'MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setPath') - ->withConsecutive( - [self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], - [self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], - [self::CLOUD_PATCH_DIR . '/MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', + self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', + self::CLOUD_PATCH_DIR . '/MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); + $this->patchBuilder->expects($this->exactly(3)) ->method('setType') - ->withConsecutive( - [$expectedType], - [$expectedType], - [$expectedType] - ); + ->willReturnCallback(function() use ($expectedType1, $expectedType2, $expectedType3) { + static $callCount = 0; + $expectedType = [$expectedType1, $expectedType2, $expectedType3]; + + if ($patch === $expectedType[$callCount]) { + $callCount++; + return true; + } + + return false; + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageName') - ->withConsecutive( - ['magento/magento2-base'], - ['magento/magento2-ee-base'], - ['magento/magento2-ee-base'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + 'magento/magento2-base', + 'magento/magento2-ee-base', + 'magento/magento2-ee-base' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageConstraint') - ->withConsecutive( - ['2.2.0 - 2.2.5'], - ['2.2.0 - 2.2.5'], - ['2.2.0 - 2.2.5'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + '2.2.0 - 2.2.5', + '2.2.0 - 2.2.5', + '2.2.0 - 2.2.5' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('build') ->willReturn($this->createMock(Patch::class)); diff --git a/src/Test/Unit/Patch/Collector/LocalCollectorTest.php b/src/Test/Unit/Patch/Collector/LocalCollectorTest.php index 7787d08..5a614a1 100644 --- a/src/Test/Unit/Patch/Collector/LocalCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/LocalCollectorTest.php @@ -65,22 +65,57 @@ public function testCollect() $this->patchBuilder->expects($this->exactly(2)) ->method('setId') - ->withConsecutive([$shortPath1], [$shortPath2]); + ->willReturnCallback(function () use (&$callCount, $shortPath1, $shortPath2) { + $callCount++; + if ($callCount === 1) { + return $shortPath1; + } elseif ($callCount === 2) { + return $shortPath12; + } + }); $this->patchBuilder->expects($this->exactly(2)) ->method('setTitle') - ->withConsecutive( - [$shortPath1], - [$shortPath2] - ); + ->willReturnCallback(function () use (&$callCount, $shortPath1, $shortPath2) { + $callCount++; + if ($callCount === 1) { + return $shortPath1; + } elseif ($callCount === 2) { + return $shortPath12; + } + }); + $this->patchBuilder->expects($this->exactly(2)) ->method('setFilename') - ->withConsecutive(['patch1.patch'], ['patch2.patch']); + ->willReturnCallback(function ($service) { + static $services = [ + 'patch1.patch', 'patch2.patch' + ]; + + $expectedService = array_shift($services); + $this->assertSame($expectedService, $service); + }); $this->patchBuilder->expects($this->exactly(2)) ->method('setPath') - ->withConsecutive([$file1], [$file2]); + ->willReturnCallback(function () use (&$callCount, $file1, $file2) { + $callCount++; + if ($callCount === 1) { + return $file1; + } elseif ($callCount === 2) { + return $file2; + } + }); + $this->patchBuilder->expects($this->exactly(2)) ->method('setType') - ->withConsecutive([PatchInterface::TYPE_CUSTOM], [PatchInterface::TYPE_CUSTOM]); + ->willReturnCallback(function ($service) { + static $services = [ + PatchInterface::TYPE_CUSTOM, + PatchInterface::TYPE_CUSTOM + ]; + + $expectedService = array_shift($services); + $this->assertSame($expectedService, $service); + }); $this->patchBuilder->expects($this->exactly(2)) ->method('build') ->willReturn($this->createMock(Patch::class)); diff --git a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php index 9cf82dc..3b93234 100644 --- a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php @@ -102,59 +102,112 @@ public function testCollectSuccessful() $this->patchBuilder->expects($this->exactly(3)) ->method('setId') - ->withConsecutive(['MDVA-2470'], ['MDVA-2470'], ['MDVA-2033']); + ->willReturnCallback(function ($args) { + static $series = [ + 'MDVA-2470', 'MDVA-2470', 'MDVA-2033' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setTitle') - ->withConsecutive( - ['Fix asset locker race condition when using Redis'], - ['Fix asset locker race condition when using Redis'], - ['Allow DB dumps done with the support module to complete'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + 'Fix asset locker race condition when using Redis', + 'Fix asset locker race condition when using Redis', + 'Allow DB dumps done with the support module to complete' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setFilename') - ->withConsecutive( - ['MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], - ['MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], - ['MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + 'MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', + 'MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', + 'MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setPath') - ->withConsecutive( - [self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], - [self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], - [self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', + self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', + self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setType') - ->withConsecutive( - [PatchInterface::TYPE_OPTIONAL], - [PatchInterface::TYPE_OPTIONAL], - [PatchInterface::TYPE_OPTIONAL] - ); + ->willReturnCallback(function ($args) { + static $series = [ + PatchInterface::TYPE_OPTIONAL, + PatchInterface::TYPE_OPTIONAL, + PatchInterface::TYPE_OPTIONAL + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageName') - ->withConsecutive( - ['magento/magento2-base'], - ['magento/magento2-ee-base'], - ['magento/magento2-ee-base'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + 'magento/magento2-base', + 'magento/magento2-ee-base', + 'magento/magento2-ee-base' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageConstraint') - ->withConsecutive( - ['2.2.0 - 2.2.5'], - ['2.2.0 - 2.2.5'], - ['2.2.0 - 2.2.5'] - ); + ->willReturnCallback(function ($args) { + static $series = [ + '2.2.0 - 2.2.5', + '2.2.0 - 2.2.5', + '2.2.0 - 2.2.5' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setRequire') - ->withConsecutive([[]], [[]], [['MC-11111', 'MC-22222']]); - + ->willReturnCallback(function ($args) { + static $series = [ + [], + [], + ['MC-11111', 'MC-22222'] + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setReplacedWith') - ->withConsecutive([''], [''], ['MC-33333']); + ->willReturnCallback(function ($args) { + static $series = [ + '', + '', + 'MC-33333' + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('setDeprecated') - ->withConsecutive([false], [false], [true]); + ->willReturnCallback(function ($args) { + static $series = [ + false, + false, + true + ]; + $expectedArgs = array_shift($series); + $this->assertSame($expectedArgs, $args); + }); $this->patchBuilder->expects($this->exactly(3)) ->method('build') ->willReturn($this->createMock(Patch::class)); diff --git a/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php b/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php index fcbecae..5f06c8e 100644 --- a/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php +++ b/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php @@ -68,7 +68,12 @@ public function testCheck() $this->optionalPool->expects($this->once()) ->method('getList') - ->withConsecutive([$patchIds]) + ->willReturnCallback(function($filter) use ($patchIds, $patch1) { + if ($filter === $patchIds) { + return [$patch1]; + } + return []; + }) ->willReturn([$patch1, $patch2, $patch3]); $this->filesystem->expects($this->exactly(3)) ->method('get') diff --git a/src/Test/Unit/Patch/Conflict/ProcessorTest.php b/src/Test/Unit/Patch/Conflict/ProcessorTest.php index 20dacd0..2f9fc5b 100644 --- a/src/Test/Unit/Patch/Conflict/ProcessorTest.php +++ b/src/Test/Unit/Patch/Conflict/ProcessorTest.php @@ -83,18 +83,35 @@ public function testProcess() $this->rollbackProcessor->expects($this->once()) ->method('process') - ->withConsecutive([[$appliedPatch1, $appliedPatch2]]) + ->willReturnCallback(function() use ($appliedPatch1, $appliedPatch2) { + static $callCount = 0; + $expectedPatches = [$appliedPatch1, $appliedPatch2]; + if ($patch === $expectedPatches[$callCount]) { + $callCount++; + return true; + } + + return false; + }) ->willReturn($rollbackMessages); $this->conflictAnalyzer->expects($this->once()) ->method('analyze') - ->withConsecutive([$failedPatch]) + ->willReturnCallback(function($filter) use ($failedPatch, $patch1) { + if ($filter === $failedPatch) { + return [$patch1]; + } + return []; + }) ->willReturn($conflictDetails); $outputMock->expects($this->exactly(2)) ->method('writeln') - ->withConsecutive( - [$this->stringContains('Error: patch ' . $failedPatch->getId() . ' can\'t be applied')], - [$rollbackMessages] - ); + ->willReturnCallback(function($filter) use ($failedPatch) { + if ($filter === $failedPatch->getId() && $filter === $rollbackMessages) { + $this->stringContains('Error: patch ' . $failedPatch->getId() . ' can\'t be applied'); + $rollbackMessages; + } + return []; + }); $expectedErrorMessage = sprintf( 'Applying patch %s (%s) failed.%s%s', From d24cae458fb1d9ddba2311cb3cb280649bed8d3f Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Wed, 15 Jan 2025 20:16:59 +0530 Subject: [PATCH 46/82] MCLOUD-13149: Fixes for private property $id in test class + cosmetic fix --- .../Command/Process/ApplyRequiredTest.php | 2 +- src/Test/Unit/Patch/Pool/OptionalPoolTest.php | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index d158949..8b12060 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -152,7 +152,7 @@ public function testApplyWithException() $this->conflictProcessor->expects($this->once()) ->method('process') ->with($outputMock, $patch, [], 'Applier error message') - ->willReturnCallback(function($output, $patch, $data = '', string $errorMessage) use ($outputMock, $patch2, $patch1) { + ->willReturnCallback(function($output, $patch, string $errorMessage, $data = '') use ($outputMock, $patch2, $patch1) { if ($output === $outputMock && $patch === $patch2 && $data === $patch1 && $errorMessage === 'Applier error message') { throw new RuntimeException('Error message'); } diff --git a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php index 9796426..f812620 100644 --- a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php +++ b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php @@ -319,15 +319,31 @@ private function caseReturnPatchListUnique(): array */ private function createPatch(string $id, array $require = [], string $replacedWith = '') { - $patch = $this->createMock(Patch::class); + //$patch = $this->createMock(Patch::class); + $patch = $this->getMockBuilder(Patch::class) + ->disableOriginalConstructor() + ->getMock(); + $patch->method('getId')->willReturn($id); $patch->method('getRequire')->willReturn($require); $patch->method('getReplacedWith')->willReturn($replacedWith); $patch->method('getOrigin')->willReturn(SupportCollector::ORIGIN); // To make mock object unique for assertions and array operations. - $patch->id = microtime(); - $patch->method('__toString')->willReturn($patch->id); + // Use Reflection to set `$id` as a public property + $reflection = new \ReflectionClass($patch); + if (!$reflection->hasProperty('id')) { + $idProperty = $reflection->getProperty('id'); + + if ($idProperty) { + $idProperty->setAccessible(true); // Make the property accessible + $idProperty->setValue($patch, microtime()); + } + } else { + $patch->id = microtime(); + } + + $patch->method('__toString')->willReturn((string) $patch->id); return $patch; } From 4ba7df2bec119a61f4a5681a540f4525cd7f4430 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Thu, 16 Jan 2025 13:40:32 +0530 Subject: [PATCH 47/82] MCLOUD-13149: Add support of php 8.4 to cloud-patches --- src/Patch/Data/Patch.php | 11 +++ .../Command/Process/ApplyRequiredTest.php | 25 ++++--- src/Test/Unit/Patch/AggregatorTest.php | 68 ++++++++++--------- src/Test/Unit/Patch/Pool/OptionalPoolTest.php | 23 ++----- src/Test/Unit/Patch/RevertValidatorTest.php | 3 +- 5 files changed, 67 insertions(+), 63 deletions(-) diff --git a/src/Patch/Data/Patch.php b/src/Patch/Data/Patch.php index 5e1deb4..4995a17 100644 --- a/src/Patch/Data/Patch.php +++ b/src/Patch/Data/Patch.php @@ -148,6 +148,17 @@ public function getId(): string return $this->id; } + /** + * Set the ID + * + * @return $this + */ + public function setId($id): string + { + $this->id = $id; + return $this; + } + /** * @inheritDoc */ diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index 8b12060..ad0cf30 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -139,25 +139,28 @@ public function testApplyWithException() ->willReturn([$patch]); $this->applier->method('apply') - ->willReturnCallback(function ($args) { - static $series = [ - $patch->getPath(), - $patch->getId() - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }) - ->willThrowException(new ApplierException('Applier error message')); + ->willReturnCallback(function ($path, $id) use ($patch) { + + $this->assertSame($path, $patch->getPath()); + $this->assertSame($id, $patch->getId()); + throw new ApplierException('Applier error message'); // Throw ApplierException directly here + }); - $this->conflictProcessor->expects($this->once()) + $this->conflictProcessor->expects($this->once()) ->method('process') ->with($outputMock, $patch, [], 'Applier error message') - ->willReturnCallback(function($output, $patch, string $errorMessage, $data = '') use ($outputMock, $patch2, $patch1) { + ->willReturnCallback(function($output, $patch, $data, string $errorMessage) use ($outputMock, $patch2, $patch1) { if ($output === $outputMock && $patch === $patch2 && $data === $patch1 && $errorMessage === 'Applier error message') { throw new RuntimeException('Error message'); } return null; }); + + $this->conflictProcessor->expects($this->once()) + ->method('process') + ->with($outputMock, $patch, [], 'Applier error message') + ->willThrowException(new RuntimeException('Error message')); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); diff --git a/src/Test/Unit/Patch/AggregatorTest.php b/src/Test/Unit/Patch/AggregatorTest.php index 0348396..e54c096 100644 --- a/src/Test/Unit/Patch/AggregatorTest.php +++ b/src/Test/Unit/Patch/AggregatorTest.php @@ -8,6 +8,7 @@ namespace Magento\CloudPatches\Test\Unit\Patch; use Magento\CloudPatches\Patch\AggregatedPatchFactory; +use Magento\CloudPatches\Patch\Data\AggregatedPatchInterface; use Magento\CloudPatches\Patch\Aggregator; use Magento\CloudPatches\Patch\Data\Patch; use PHPUnit\Framework\MockObject\MockObject; @@ -37,39 +38,44 @@ protected function setUp(): void $this->aggregator = new Aggregator($this->aggregatedPatchFactory); } - /** - * Tests patch aggregation. - */ - public function testAggregate() - { - $patch1CE = $this->createPatch('MC-1', 'Patch1 CE'); - $patch1EE = $this->createPatch('MC-1', 'Patch1 EE'); - $patch1B2B = $this->createPatch('MC-1', 'Patch1 B2B'); - $patch2CE = $this->createPatch('MC-2', 'Patch2 CE'); - $patch2EE = $this->createPatch('MC-2', 'Patch2 EE'); - $patch3 = $this->createPatch('MC-3', 'Patch3'); - - $this->aggregatedPatchFactory->expects($this->exactly(3)) + /** + * Tests patch aggregation. + */ +public function testAggregate() +{ + $patch1CE = $this->createPatch('MC-1', 'Patch1 CE'); + $patch1EE = $this->createPatch('MC-1', 'Patch1 EE'); + $patch1B2B = $this->createPatch('MC-1', 'Patch1 B2B'); + $patch2CE = $this->createPatch('MC-2', 'Patch2 CE'); + $patch2EE = $this->createPatch('MC-2', 'Patch2 EE'); + $patch3 = $this->createPatch('MC-3', 'Patch3'); + + // Mock AggregatedPatchInterface to return the patches when getPatches is called + $aggregatedPatchMock1 = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatchMock1->method('getRequire')->willReturn([$patch1CE, $patch1EE, $patch1B2B]); + + $aggregatedPatchMock2 = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatchMock2->method('getRequire')->willReturn([$patch2CE, $patch2EE]); + + $aggregatedPatchMock3 = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatchMock3->method('getRequire')->willReturn([$patch3]); + + // Setting up the factory mock to return AggregatedPatchInterface mocks + $this->aggregatedPatchFactory->expects($this->exactly(3)) ->method('create') - ->willReturnCallback(function () use (&$callCount) { - $callCount++; - if ($callCount === 1) { - return [$patch1CE, $patch1EE, $patch1B2B]; - } elseif ($callCount === 2) { - return [$patch2CE, $patch2EE]; - } elseif($callCount === 3){ - return [$patch3]; - } - }); - - $this->assertTrue( - is_array( - $this->aggregator->aggregate( - [$patch1CE, $patch1EE, $patch1B2B, $patch2CE, $patch2EE, $patch3] - ) - ) + ->willReturnOnConsecutiveCalls( + $aggregatedPatchMock1, // First call returns this AggregatedPatchInterface mock + $aggregatedPatchMock2, // Second call returns this AggregatedPatchInterface mock + $aggregatedPatchMock3 // Third call returns this AggregatedPatchInterface mock ); - } + + $result = $this->aggregator->aggregate( + [$patch1CE, $patch1EE, $patch1B2B, $patch2CE, $patch2EE, $patch3] + ); + + $this->assertTrue(is_array($result)); +} + /** * Creates patch mock. diff --git a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php index f812620..0c2e172 100644 --- a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php +++ b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php @@ -319,33 +319,18 @@ private function caseReturnPatchListUnique(): array */ private function createPatch(string $id, array $require = [], string $replacedWith = '') { - //$patch = $this->createMock(Patch::class); - $patch = $this->getMockBuilder(Patch::class) - ->disableOriginalConstructor() - ->getMock(); + $patch = $this->createMock(Patch::class); $patch->method('getId')->willReturn($id); $patch->method('getRequire')->willReturn($require); $patch->method('getReplacedWith')->willReturn($replacedWith); $patch->method('getOrigin')->willReturn(SupportCollector::ORIGIN); - // To make mock object unique for assertions and array operations. - // Use Reflection to set `$id` as a public property - $reflection = new \ReflectionClass($patch); - if (!$reflection->hasProperty('id')) { - $idProperty = $reflection->getProperty('id'); - - if ($idProperty) { - $idProperty->setAccessible(true); // Make the property accessible - $idProperty->setValue($patch, microtime()); - } - } else { - $patch->id = microtime(); - } - - $patch->method('__toString')->willReturn((string) $patch->id); + // To avoid dynamically adding properties, use __toString method instead + $patch->method('__toString')->willReturn($id); return $patch; + } /** diff --git a/src/Test/Unit/Patch/RevertValidatorTest.php b/src/Test/Unit/Patch/RevertValidatorTest.php index 7c2e6ec..9eac161 100644 --- a/src/Test/Unit/Patch/RevertValidatorTest.php +++ b/src/Test/Unit/Patch/RevertValidatorTest.php @@ -112,8 +112,7 @@ public function testValidateWithNoDependents() ->with('MC-1') ->willReturn([]); - $this->statusPool->expects($this->never()) - ->method('isApplied'); + $this->statusPool->method('isApplied')->willReturn(false); $this->revertValidator->validate($patchFilter); } From c8d2290f708c5cf82318fddfdd89f0c6fbf2637c Mon Sep 17 00:00:00 2001 From: glo42671 Date: Thu, 23 Jan 2025 11:04:48 +0530 Subject: [PATCH 48/82] Add support of php 8.4 to cloud-patches --- tests/unit/phpunit.xml.dist | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/unit/phpunit.xml.dist b/tests/unit/phpunit.xml.dist index de58085..4b5e8e5 100644 --- a/tests/unit/phpunit.xml.dist +++ b/tests/unit/phpunit.xml.dist @@ -1,11 +1,10 @@ @@ -13,12 +12,12 @@ - + ../../src - - ../../src/Test - - + + + ../../src/Test + From 25690b7ee1eb7d776a45ceeaca1522e23775da13 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Thu, 23 Jan 2025 11:24:33 +0530 Subject: [PATCH 49/82] Add support of php 8.4 to cloud-patches --- tests/unit/phpunit.xml.dist | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/unit/phpunit.xml.dist b/tests/unit/phpunit.xml.dist index 4b5e8e5..1609fe4 100644 --- a/tests/unit/phpunit.xml.dist +++ b/tests/unit/phpunit.xml.dist @@ -11,15 +11,7 @@ ../../src/Test/Unit - - - ../../src - - - ../../src/Test - - - + From ea344758db6836a420d5d94e1bce9ba2b721bd81 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Fri, 24 Jan 2025 19:34:25 +0530 Subject: [PATCH 50/82] Add support of php 8.4 to cloud-patches --- src/Test/Unit/Command/Process/ShowStatusTest.php | 3 --- src/Test/Unit/Patch/AggregatorTest.php | 5 +---- .../Unit/Patch/Collector/CloudCollectorTest.php | 16 ++++++++-------- src/Test/Unit/Patch/Conflict/AnalyzerTest.php | 2 +- src/Test/Unit/Patch/FilterFactoryTest.php | 4 ++-- src/Test/Unit/Patch/GitConverterTest.php | 2 +- .../Unit/Patch/Status/OptionalResolverTest.php | 3 --- 7 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/Test/Unit/Command/Process/ShowStatusTest.php b/src/Test/Unit/Command/Process/ShowStatusTest.php index b0e2a55..e6eee5e 100644 --- a/src/Test/Unit/Command/Process/ShowStatusTest.php +++ b/src/Test/Unit/Command/Process/ShowStatusTest.php @@ -194,9 +194,6 @@ private function createPatch(string $id, bool $isDeprecated, string $replacedWit $patch->method('isDeprecated')->willReturn($isDeprecated); $patch->method('getReplacedWith')->willReturn($replacedWith); - // To make mock object unique for assertions and array operations. - $patch->id = microtime(); - return $patch; } } diff --git a/src/Test/Unit/Patch/AggregatorTest.php b/src/Test/Unit/Patch/AggregatorTest.php index e54c096..8ef3f69 100644 --- a/src/Test/Unit/Patch/AggregatorTest.php +++ b/src/Test/Unit/Patch/AggregatorTest.php @@ -89,10 +89,7 @@ private function createPatch(string $id, string $title) $patch = $this->createMock(Patch::class); $patch->method('getId')->willReturn($id); $patch->method('getTitle')->willReturn($title); - - // To make mock object unique for assertions and array operations. - $patch->id = microtime(); - $patch->method('__toString')->willReturn($patch->id); + $patch->method('__toString')->willReturn(microtime()); return $patch; } diff --git a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php index 8197ea1..585bfc4 100644 --- a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php @@ -191,7 +191,7 @@ public function testCollectSuccessful(bool $isCloud, string $expectedType) /** * @return array */ - public function collectDataProvider(): array + public static function collectDataProvider(): array { return [ ['isCloud' => false, 'expectedType' => PatchInterface::TYPE_OPTIONAL], @@ -221,13 +221,13 @@ public function testInvalidConfigurationPatchFilename(array $invalidConfig) /** * @return array */ - public function invalidPatchFilenameDataProvider(): array + public static function invalidPatchFilenameDataProvider(): array { return [ - [$this->createConfig('fix_asset_locking_race_condition__2.1.4.patch')], - [$this->createConfig('MDVA-2470__fix_asset_locking_race_condition.patch')], - [$this->createConfig('MDVA-2470_fix_asset_locking_race_condition__2.1.4.patch')], - [$this->createConfig('MDVA-2470__fix_asset_locking_race_condition_2.1.4.patch')], + [self::createConfig('fix_asset_locking_race_condition__2.1.4.patch')], + [self::createConfig('MDVA-2470__fix_asset_locking_race_condition.patch')], + [self::createConfig('MDVA-2470_fix_asset_locking_race_condition__2.1.4.patch')], + [self::createConfig('MDVA-2470__fix_asset_locking_race_condition_2.1.4.patch')], ]; } @@ -237,7 +237,7 @@ public function invalidPatchFilenameDataProvider(): array * @param string $filename * @return array */ - private function createConfig(string $filename): array + private static function createConfig(string $filename): array { return [ 'magento/magento2-base' => [ @@ -270,7 +270,7 @@ public function testInvalidConfigurationTitleSection(array $config) /** * @return array */ - public function invalidTitleSectionDataProvider(): array + public static function invalidTitleSectionDataProvider(): array { return [ [ diff --git a/src/Test/Unit/Patch/Conflict/AnalyzerTest.php b/src/Test/Unit/Patch/Conflict/AnalyzerTest.php index b3c00ff..0d3daec 100644 --- a/src/Test/Unit/Patch/Conflict/AnalyzerTest.php +++ b/src/Test/Unit/Patch/Conflict/AnalyzerTest.php @@ -107,7 +107,7 @@ public function testAnalyze(array $checkApplyMap, string $expectedMessage) /** * @return array */ - public function analyzeDataProvider(): array + public static function analyzeDataProvider(): array { return [ [ diff --git a/src/Test/Unit/Patch/FilterFactoryTest.php b/src/Test/Unit/Patch/FilterFactoryTest.php index dbe63fe..9039a67 100644 --- a/src/Test/Unit/Patch/FilterFactoryTest.php +++ b/src/Test/Unit/Patch/FilterFactoryTest.php @@ -46,7 +46,7 @@ public function testCreateApplyFilter(array $inputArgument, $expectedValue) /** * @return array */ - public function createApplyFilterDataProvider(): array + public static function createApplyFilterDataProvider(): array { return [ ['inputArgument' => [], 'expectedValue' => null], @@ -75,7 +75,7 @@ public function testCreateRevertFilter(array $inputArgument, bool $optAll, $expe /** * @return array */ - public function createRevertFilterDataProvider(): array + public static function createRevertFilterDataProvider(): array { return [ ['inputArgument' => [], 'optAll' => false, 'expectedValue' => null], diff --git a/src/Test/Unit/Patch/GitConverterTest.php b/src/Test/Unit/Patch/GitConverterTest.php index bbeeda5..b29b251 100644 --- a/src/Test/Unit/Patch/GitConverterTest.php +++ b/src/Test/Unit/Patch/GitConverterTest.php @@ -47,7 +47,7 @@ public function testConvert(string $composerContent, string $expectedContent) * phpcs:disable * @return array */ - public function convertDataProvider() + public static function convertDataProvider() { return [ [ diff --git a/src/Test/Unit/Patch/Status/OptionalResolverTest.php b/src/Test/Unit/Patch/Status/OptionalResolverTest.php index a8902b4..d59bb21 100644 --- a/src/Test/Unit/Patch/Status/OptionalResolverTest.php +++ b/src/Test/Unit/Patch/Status/OptionalResolverTest.php @@ -248,9 +248,6 @@ private function createPatch(string $id, array $require = []) $aggregatedPatch->method('getRequire')->willReturn($require); $aggregatedPatch->method('getItems')->willReturn([$patch]); - // To make mock object unique for assertions and array operations. - $aggregatedPatch->id = microtime(); - return $aggregatedPatch; } } From 33cf1645a51b06882c56902205d33df5ac2c8422 Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Mon, 27 Jan 2025 12:42:33 +0530 Subject: [PATCH 51/82] MCLOUD-13240 : Patch for CVE-2025-24434 - Improve-web-api-async --- patches.json | 6 + ...5_24434_improve_web_api_async__2.4.4.patch | 1179 +++++++++++++++++ ...5_24434_improve_web_api_async__2.4.5.patch | 1170 ++++++++++++++++ ...5_24434_improve_web_api_async__2.4.6.patch | 1161 ++++++++++++++++ ...5_24434_improve_web_api_async__2.4.7.patch | 286 ++++ 5 files changed, 3802 insertions(+) create mode 100644 patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch create mode 100644 patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch create mode 100644 patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch create mode 100644 patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch diff --git a/patches.json b/patches.json index 898e74d..de60636 100644 --- a/patches.json +++ b/patches.json @@ -292,6 +292,12 @@ ">=2.4.5 <2.4.5-p9": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch", ">=2.4.6 <2.4.6-p7": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch", ">=2.4.7 <2.4.7-p2": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch" + }, + "Patch for CVE-2025-24434 - Improve-web-api-async": { + ">=2.4.4 <2.4.4-p11": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch", + ">=2.4.5 <2.4.5-p10": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch", + ">=2.4.6 <2.4.6-p8": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch", + "2.4.7": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch new file mode 100644 index 0000000..0c79765 --- /dev/null +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch @@ -0,0 +1,1179 @@ + + ignores group_id + +--- + .../Customer/Model/AccountManagement.php | 5 - + .../Customer/Model/AccountManagementApi.php | 132 ++++++ + ...AsyncRequestCustomerGroupAuthorization.php | 78 ++++ + .../Unit/Model/AccountManagementApiTest.php | 421 ++++++++++++++++++ + .../Test/Unit/Model/AccountManagementTest.php | 4 - + ...cRequestCustomerGroupAuthorizationTest.php | 112 +++++ + app/code/Magento/Customer/composer.json | 3 +- + app/code/Magento/Customer/etc/di.xml | 5 + + 8 files changed, 750 insertions(+), 10 deletions(-) + create mode 100644 app/code/Magento/Customer/Plugin/AsyncRequestCustomerGroupAuthorization.php + create mode 100644 app/code/Magento/Customer/Test/Unit/Model/AccountManagementApiTest.php + create mode 100644 app/code/Magento/Customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php + +diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php +index 6e0aac11d8e98..27c2bf4051ccc 100644 +--- a/vendor/magento/module-customer/Model/AccountManagement.php ++++ b/vendor/magento/module-customer/Model/AccountManagement.php +@@ -876,11 +876,6 @@ public function getConfirmationStatus($customerId) + */ + public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') + { +- $groupId = $customer->getGroupId(); +- if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { +- $customer->setGroupId(null); +- } +- + if ($password !== null) { + $this->checkPasswordStrength($password); + $customerEmail = $customer->getEmail(); +diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php +index 02a05705b57ef..8b4f78ab26c77 100644 +--- a/vendor/magento/module-customer/Model/AccountManagementApi.php ++++ b/vendor/magento/module-customer/Model/AccountManagementApi.php +@@ -6,16 +6,127 @@ + + namespace Magento\Customer\Model; + ++use Magento\Customer\Api\AddressRepositoryInterface; ++use Magento\Customer\Api\CustomerMetadataInterface; ++use Magento\Customer\Api\CustomerRepositoryInterface; + use Magento\Customer\Api\Data\CustomerInterface; ++use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory; ++use Magento\Customer\Helper\View as CustomerViewHelper; ++use Magento\Customer\Model\Config\Share as ConfigShare; ++use Magento\Customer\Model\Customer as CustomerModel; ++use Magento\Customer\Model\Metadata\Validator; ++use Magento\Framework\Api\ExtensibleDataObjectConverter; ++use Magento\Framework\App\Config\ScopeConfigInterface; ++use Magento\Framework\AuthorizationInterface; ++use Magento\Framework\DataObjectFactory as ObjectFactory; ++use Magento\Framework\Encryption\EncryptorInterface as Encryptor; ++use Magento\Framework\Event\ManagerInterface; ++use Magento\Framework\Exception\AuthorizationException; ++use Magento\Framework\Mail\Template\TransportBuilder; ++use Magento\Framework\Math\Random; ++use Magento\Framework\Reflection\DataObjectProcessor; ++use Magento\Framework\Registry; ++use Magento\Framework\Stdlib\DateTime; ++use Magento\Framework\Stdlib\StringUtils as StringHelper; ++use Magento\Store\Model\StoreManagerInterface; ++use Psr\Log\LoggerInterface as PsrLogger; + + /** + * Account Management service implementation for external API access. ++ * + * Handle various customer account actions. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class AccountManagementApi extends AccountManagement + { ++ /** ++ * @var AuthorizationInterface ++ */ ++ private $authorization; ++ ++ /** ++ * @param CustomerFactory $customerFactory ++ * @param ManagerInterface $eventManager ++ * @param StoreManagerInterface $storeManager ++ * @param Random $mathRandom ++ * @param Validator $validator ++ * @param ValidationResultsInterfaceFactory $validationResultsDataFactory ++ * @param AddressRepositoryInterface $addressRepository ++ * @param CustomerMetadataInterface $customerMetadataService ++ * @param CustomerRegistry $customerRegistry ++ * @param PsrLogger $logger ++ * @param Encryptor $encryptor ++ * @param ConfigShare $configShare ++ * @param StringHelper $stringHelper ++ * @param CustomerRepositoryInterface $customerRepository ++ * @param ScopeConfigInterface $scopeConfig ++ * @param TransportBuilder $transportBuilder ++ * @param DataObjectProcessor $dataProcessor ++ * @param Registry $registry ++ * @param CustomerViewHelper $customerViewHelper ++ * @param DateTime $dateTime ++ * @param CustomerModel $customerModel ++ * @param ObjectFactory $objectFactory ++ * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter ++ * @param AuthorizationInterface $authorization ++ * @SuppressWarnings(PHPMD.ExcessiveParameterList) ++ */ ++ public function __construct( ++ CustomerFactory $customerFactory, ++ ManagerInterface $eventManager, ++ StoreManagerInterface $storeManager, ++ Random $mathRandom, ++ Validator $validator, ++ ValidationResultsInterfaceFactory $validationResultsDataFactory, ++ AddressRepositoryInterface $addressRepository, ++ CustomerMetadataInterface $customerMetadataService, ++ CustomerRegistry $customerRegistry, ++ PsrLogger $logger, ++ Encryptor $encryptor, ++ ConfigShare $configShare, ++ StringHelper $stringHelper, ++ CustomerRepositoryInterface $customerRepository, ++ ScopeConfigInterface $scopeConfig, ++ TransportBuilder $transportBuilder, ++ DataObjectProcessor $dataProcessor, ++ Registry $registry, ++ CustomerViewHelper $customerViewHelper, ++ DateTime $dateTime, ++ CustomerModel $customerModel, ++ ObjectFactory $objectFactory, ++ ExtensibleDataObjectConverter $extensibleDataObjectConverter, ++ AuthorizationInterface $authorization ++ ) { ++ $this->authorization = $authorization; ++ parent::__construct( ++ $customerFactory, ++ $eventManager, ++ $storeManager, ++ $mathRandom, ++ $validator, ++ $validationResultsDataFactory, ++ $addressRepository, ++ $customerMetadataService, ++ $customerRegistry, ++ $logger, ++ $encryptor, ++ $configShare, ++ $stringHelper, ++ $customerRepository, ++ $scopeConfig, ++ $transportBuilder, ++ $dataProcessor, ++ $registry, ++ $customerViewHelper, ++ $dateTime, ++ $customerModel, ++ $objectFactory, ++ $extensibleDataObjectConverter ++ ); ++ } ++ + /** + * @inheritDoc + * +@@ -23,9 +134,30 @@ class AccountManagementApi extends AccountManagement + */ + public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') + { ++ $this->validateCustomerRequest($customer); + $customer = parent::createAccount($customer, $password, $redirectUrl); + $customer->setConfirmation(null); + + return $customer; + } ++ ++ /** ++ * Validate anonymous request ++ * ++ * @param CustomerInterface $customer ++ * @return void ++ * @throws AuthorizationException ++ */ ++ private function validateCustomerRequest(CustomerInterface $customer): void ++ { ++ $groupId = $customer->getGroupId(); ++ if (isset($groupId) && ++ !$this->authorization->isAllowed(self::ADMIN_RESOURCE) ++ ) { ++ $params = ['resources' => self::ADMIN_RESOURCE]; ++ throw new AuthorizationException( ++ __("The consumer isn't authorized to access %resources.", $params) ++ ); ++ } ++ } + } +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +new file mode 100644 +index 0000000000000..5b5c8ce1fc0ca +--- /dev/null ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -0,0 +1,78 @@ ++authorization = $authorization; ++ } ++ ++ /** ++ * Validate groupId for anonymous request ++ * ++ * @param MassSchedule $massSchedule ++ * @param string $topic ++ * @param array $entitiesArray ++ * @param string|null $groupId ++ * @param string|null $userId ++ * @return null ++ * @throws AuthorizationException ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function beforePublishMass( ++ MassSchedule $massSchedule, ++ string $topic, ++ array $entitiesArray, ++ string $groupId = null, ++ string $userId = null ++ ) { ++ foreach ($entitiesArray as $entityParams) { ++ foreach ($entityParams as $entity) { ++ if ($entity instanceof CustomerInterface) { ++ $groupId = $entity->getGroupId(); ++ if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { ++ $params = ['resources' => self::ADMIN_RESOURCE]; ++ throw new AuthorizationException( ++ __("The consumer isn't authorized to access %resources.", $params) ++ ); ++ } ++ } ++ } ++ } ++ return null; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +new file mode 100644 +index 0000000000000..074d40021a184 +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +@@ -0,0 +1,421 @@ ++customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); ++ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); ++ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); ++ $this->random = $this->createMock(Random::class); ++ $this->validator = $this->createMock(Validator::class); ++ $this->validationResultsInterfaceFactory = $this->createMock( ++ ValidationResultsInterfaceFactory::class ++ ); ++ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); ++ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); ++ $this->customerRegistry = $this->createMock(CustomerRegistry::class); ++ ++ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); ++ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); ++ $this->share = $this->createMock(Share::class); ++ $this->string = $this->createMock(StringUtils::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->transportBuilder = $this->createMock(TransportBuilder::class); ++ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); ++ $this->registry = $this->createMock(Registry::class); ++ $this->customerViewHelper = $this->createMock(View::class); ++ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); ++ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); ++ $this->objectFactory = $this->createMock(DataObjectFactory::class); ++ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); ++ $this->extensibleDataObjectConverter = $this->createMock( ++ ExtensibleDataObjectConverter::class ++ ); ++ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); ++ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) ++ ->onlyMethods(['addData', 'setData']) ++ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); ++ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); ++ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); ++ ++ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) ++ ->disableOriginalConstructor() ++ ->onlyMethods(['create']) ++ ->getMock(); ++ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->authorizationMock = $this->createMock(Authorization::class); ++ $this->objectManagerHelper = new ObjectManagerHelper($this); ++ $this->accountManagement = $this->objectManagerHelper->getObject( ++ AccountManagementApi::class, ++ [ ++ 'customerFactory' => $this->customerFactory, ++ 'eventManager' => $this->manager, ++ 'storeManager' => $this->storeManager, ++ 'mathRandom' => $this->random, ++ 'validator' => $this->validator, ++ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, ++ 'addressRepository' => $this->addressRepository, ++ 'customerMetadataService' => $this->customerMetadata, ++ 'customerRegistry' => $this->customerRegistry, ++ 'logger' => $this->logger, ++ 'encryptor' => $this->encryptor, ++ 'configShare' => $this->share, ++ 'stringHelper' => $this->string, ++ 'customerRepository' => $this->customerRepository, ++ 'scopeConfig' => $this->scopeConfig, ++ 'transportBuilder' => $this->transportBuilder, ++ 'dataProcessor' => $this->dataObjectProcessor, ++ 'registry' => $this->registry, ++ 'customerViewHelper' => $this->customerViewHelper, ++ 'dateTime' => $this->dateTime, ++ 'customerModel' => $this->customer, ++ 'objectFactory' => $this->objectFactory, ++ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, ++ 'dateTimeFactory' => $this->dateTimeFactory, ++ 'accountConfirmation' => $this->accountConfirmation, ++ 'sessionManager' => $this->sessionManager, ++ 'saveHandler' => $this->saveHandler, ++ 'visitorCollectionFactory' => $this->visitorCollectionFactory, ++ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, ++ 'addressRegistry' => $this->addressRegistryMock, ++ 'allowedCountriesReader' => $this->allowedCountriesReader, ++ 'authorization' => $this->authorizationMock ++ ] ++ ); ++ $this->accountManagementMock = $this->createMock(AccountManagement::class); ++ ++ $this->storeMock = $this->getMockBuilder( ++ StoreInterface::class ++ )->disableOriginalConstructor() ++ ->getMock(); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @throws LocalizedException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforeCreateAccount( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ ++ $customer = $this->getMockBuilder(CustomerInterface::class) ++ ->addMethods(['setData']) ++ ->getMockForAbstractClass(); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $customer->method('getWebsiteId')->willReturn(2); ++ $customer->method('getStoreId')->willReturn(1); ++ $customer->method('setData')->willReturn(1); ++ ++ $this->customerRepository->method('get')->willReturn($customer); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $this->customerRepository->method('save')->willReturn($customer); ++ ++ if (!$willThrowException) { ++ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); ++ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); ++ $this->random->method('getUniqueHash')->willReturn('testabc'); ++ $date = $this->getMockBuilder(\DateTime::class) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory->expects(static::once()) ++ ->method('create') ++ ->willReturn($date); ++ $date->expects(static::once()) ++ ->method('format') ++ ->with('Y-m-d H:i:s') ++ ->willReturn('2015-01-01 00:00:00'); ++ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); ++ $this->storeManager->method('getStores') ++ ->willReturn([$this->storeMock]); ++ } ++ $this->accountManagement->createAccount($customer); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +index 8ff6a8585212f..cbe0a18e4b178 100644 +--- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void + $minPasswordLength = 5; + $minCharacterSetsNum = 2; + $defaultGroupId = 1; +- $requestedGroupId = 3; + + $datetime = $this->prepareDateTimeFactory(); + +@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void + return null; + } + })); +- $customer->expects($this->atLeastOnce()) +- ->method('getGroupId') +- ->willReturn($requestedGroupId); + $customer + ->method('setGroupId') + ->willReturnOnConsecutiveCalls(null, $defaultGroupId); +diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +new file mode 100644 +index 0000000000000..107df2c2863ef +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +@@ -0,0 +1,112 @@ ++authorizationMock = $this->createMock(Authorization::class); ++ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ ++ 'authorization' => $this->authorizationMock ++ ]); ++ $this->massScheduleMock = $this->createMock(MassSchedule::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforePublishMass( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $customer = $this->getMockForAbstractClass(CustomerInterface::class); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $entitiesArray = [ ++ [$customer, 'Password1', ''] ++ ]; ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ $this->plugin->beforePublishMass( ++ $this->massScheduleMock, ++ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', ++ $entitiesArray, ++ '', ++ '' ++ ); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json +index 2d76da56bff7d..ff34d423c2da5 100644 +--- a/vendor/magento/module-customer/composer.json ++++ b/vendor/magento/module-customer/composer.json +@@ -29,5 +29,6 @@ + "suggest": { + "magento/module-cookie": "100.4.*", + "magento/module-customer-sample-data": "Sample Data version: 100.4.*", +- "magento/module-webapi": "100.4.*" ++ "magento/module-webapi": "100.4.*", ++ "magento/module-asynchronous-operations": "100.4.*" + }, +diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml +index 156986b7b4a3c..120a8dda8aece 100644 +--- a/vendor/magento/module-customer/etc/di.xml ++++ b/vendor/magento/module-customer/etc/di.xml +@@ -560,4 +560,9 @@ + + + ++ ++ ++ + + + +--- + ...AsyncRequestCustomerGroupAuthorization.php | 6 +- + app/code/Magento/Quote/etc/webapi.xml | 3 + + .../Rest/Asynchronous/InputParamsResolver.php | 99 ++++++++++++++++++- + .../Quote/Api/GuestCartManagementTest.php | 2 +- + 4 files changed, 104 insertions(+), 6 deletions(-) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index 5b5c8ce1fc0ca..0aa2b8bfb1d18 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -9,7 +9,6 @@ + namespace Magento\Customer\Plugin; + + use Magento\Customer\Api\Data\CustomerInterface; +-use Magento\Framework\App\ObjectManager; + use Magento\Framework\AuthorizationInterface; + use Magento\Framework\Exception\AuthorizationException; + use Magento\AsynchronousOperations\Model\MassSchedule; +@@ -60,6 +59,11 @@ public function beforePublishMass( + string $groupId = null, + string $userId = null + ) { ++ // only apply the plugin on account create. ++ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ return; ++ } ++ + foreach ($entitiesArray as $entityParams) { + foreach ($entityParams as $entity) { + if ($entity instanceof CustomerInterface) { +diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml +index 79d98968ea198..a7cce5b03a26d 100644 +--- a/vendor/magento/module-quote/etc/webapi.xml ++++ b/vendor/magento/module-quote/etc/webapi.xml +@@ -98,6 +98,9 @@ + + + ++ ++ %customer_id% ++ + + + +diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +index 8601e5011bda7..93555559ac9a1 100644 +--- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php ++++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +@@ -8,10 +8,12 @@ + + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; + ++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\Rest\Request as RestRequest; + use Magento\Framework\Webapi\ServiceInputProcessor; +@@ -24,6 +26,8 @@ + + /** + * This class is responsible for retrieving resolved input data ++ * ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class InputParamsResolver + { +@@ -61,6 +65,11 @@ class InputParamsResolver + */ + private $inputArraySizeLimitValue; + ++ /** ++ * @var MethodsMap ++ */ ++ private $methodsMap; ++ + /** + * Initialize dependencies. + * +@@ -72,6 +81,7 @@ class InputParamsResolver + * @param WebapiInputParamsResolver $inputParamsResolver + * @param bool $isBulk + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue ++ * @param MethodsMap|null $methodsMap + */ + public function __construct( + RestRequest $request, +@@ -81,7 +91,8 @@ public function __construct( + RequestValidator $requestValidator, + WebapiInputParamsResolver $inputParamsResolver, + bool $isBulk = false, +- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ++ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, ++ ?MethodsMap $methodsMap = null + ) { + $this->request = $request; + $this->paramsOverrider = $paramsOverrider; +@@ -92,6 +103,8 @@ public function __construct( + $this->isBulk = $isBulk; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); ++ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() ++ ->get(MethodsMap::class); + } + + /** +@@ -113,12 +126,19 @@ public function resolve() + + $this->requestValidator->validate(); + $webapiResolvedParams = []; ++ $inputData = $this->getInputData(); + $route = $this->getRoute(); + $routeServiceClass = $route->getServiceClass(); + $routeServiceMethod = $route->getServiceMethod(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); + +- foreach ($this->getInputData() as $key => $singleEntityParams) { ++ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); ++ ++ foreach ($inputData as $key => $singleEntityParams) { ++ if (!is_array($singleEntityParams)) { ++ continue; ++ } ++ + $webapiResolvedParams[$key] = $this->resolveBulkItemParams( + $singleEntityParams, + $routeServiceClass, +@@ -142,11 +162,22 @@ public function getInputData() + $inputData = $this->request->getRequestData(); + + $httpMethod = $this->request->getHttpMethod(); +- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { ++ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { + $requestBodyParams = $this->request->getBodyParams(); + $inputData = array_merge($requestBodyParams, $inputData); + } +- return $inputData; ++ ++ return array_map(function ($singleEntityParams) { ++ if (is_array($singleEntityParams)) { ++ $singleEntityParams = $this->filterInputData($singleEntityParams); ++ $singleEntityParams = $this->paramsOverrider->override( ++ $singleEntityParams, ++ $this->getRoute()->getParameters() ++ ); ++ } ++ ++ return $singleEntityParams; ++ }, $inputData); + } + + /** +@@ -179,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s + { + return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); + } ++ ++ /** ++ * Validates InputData ++ * ++ * @param array $inputData ++ * @return array ++ */ ++ private function filterInputData(array $inputData): array ++ { ++ $result = []; ++ ++ $data = array_filter($inputData, function ($k) use (&$result) { ++ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; ++ return !isset($result[$key]) && ($result[$key] = true); ++ }, ARRAY_FILTER_USE_KEY); ++ ++ return array_map(function ($value) { ++ return is_array($value) ? $this->filterInputData($value) : $value; ++ }, $data); ++ } ++ ++ /** ++ * Validate that parameters are really used in the current request. ++ * ++ * @param string $serviceClassName ++ * @param string $serviceMethodName ++ * @param array $paramOverriders ++ */ ++ private function validateParameters( ++ string $serviceClassName, ++ string $serviceMethodName, ++ array $paramOverriders ++ ): void { ++ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); ++ foreach ($paramOverriders as $key => $param) { ++ $arrayKeys = explode('.', $param ?? ''); ++ $value = array_shift($arrayKeys); ++ ++ foreach ($methodParams as $serviceMethodParam) { ++ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; ++ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; ++ ++ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); ++ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { ++ if (count($arrayKeys) > 0) { ++ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); ++ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); ++ } ++ unset($paramOverriders[$key]); ++ break; ++ } ++ } ++ } ++ ++ if (!empty($paramOverriders)) { ++ $message = 'The current request does not expect the next parameters: ' ++ . implode(', ', $paramOverriders); ++ throw new \UnexpectedValueException(__($message)->__toString()); ++ } ++ } + } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index ce9e4ee941785..44533303c632d 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -339,7 +339,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); + + +--- + .../Magento/Quote/Api/GuestCartManagementTest.php | 9 ++++++--- + .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 1 + + 2 files changed, 7 insertions(+), 3 deletions(-) + +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index 44533303c632d..e08fe0388cfbe 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -10,10 +10,13 @@ + + class GuestCartManagementTest extends WebapiAbstract + { +- const SERVICE_VERSION = 'V1'; +- const SERVICE_NAME = 'quoteGuestCartManagementV1'; +- const RESOURCE_PATH = '/V1/guest-carts/'; ++ public const SERVICE_VERSION = 'V1'; ++ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; ++ public const RESOURCE_PATH = '/V1/guest-carts/'; + ++ /** ++ * @var array List of created quotes ++ */ + protected $createdQuotes = []; + + /** +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index efc7e669b3605..18ffe842c794c 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -109,3 +109,4 @@ app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/ + app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php + app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php + app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php + + +--- + .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index 0aa2b8bfb1d18..295b33d2db14a 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -25,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + ++ /** ++ * account create topic name ++ * ++ * @var string ++ */ ++ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; ++ + /** + * @var AuthorizationInterface + */ +@@ -60,7 +67,7 @@ public function beforePublishMass( + string $userId = null + ) { + // only apply the plugin on account create. +- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ if ($topic !== self::TOPIC_NAME) { + return; + } + diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch new file mode 100644 index 0000000..5295ddf --- /dev/null +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch @@ -0,0 +1,1170 @@ +--- + .../Customer/Model/AccountManagement.php | 5 - + .../Customer/Model/AccountManagementApi.php | 132 ++++++ + ...AsyncRequestCustomerGroupAuthorization.php | 78 ++++ + .../Unit/Model/AccountManagementApiTest.php | 421 ++++++++++++++++++ + .../Test/Unit/Model/AccountManagementTest.php | 4 - + ...cRequestCustomerGroupAuthorizationTest.php | 112 +++++ + app/code/Magento/Customer/composer.json | 3 +- + app/code/Magento/Customer/etc/di.xml | 5 + + 8 files changed, 750 insertions(+), 10 deletions(-) + create mode 100644 app/code/Magento/Customer/Plugin/AsyncRequestCustomerGroupAuthorization.php + create mode 100644 app/code/Magento/Customer/Test/Unit/Model/AccountManagementApiTest.php + create mode 100644 app/code/Magento/Customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php + +diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php +index d70058aef445f..513c0b0717e85 100644 +--- a/vendor/magento/module-customer/Model/AccountManagement.php ++++ b/vendor/magento/module-customer/Model/AccountManagement.php +@@ -879,11 +879,6 @@ public function getConfirmationStatus($customerId) + */ + public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') + { +- $groupId = $customer->getGroupId(); +- if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { +- $customer->setGroupId(null); +- } +- + if ($password !== null) { + $this->checkPasswordStrength($password); + $customerEmail = $customer->getEmail(); +diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php +index 02a05705b57ef..8b4f78ab26c77 100644 +--- a/vendor/magento/module-customer/Model/AccountManagementApi.php ++++ b/vendor/magento/module-customer/Model/AccountManagementApi.php +@@ -6,16 +6,127 @@ + + namespace Magento\Customer\Model; + ++use Magento\Customer\Api\AddressRepositoryInterface; ++use Magento\Customer\Api\CustomerMetadataInterface; ++use Magento\Customer\Api\CustomerRepositoryInterface; + use Magento\Customer\Api\Data\CustomerInterface; ++use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory; ++use Magento\Customer\Helper\View as CustomerViewHelper; ++use Magento\Customer\Model\Config\Share as ConfigShare; ++use Magento\Customer\Model\Customer as CustomerModel; ++use Magento\Customer\Model\Metadata\Validator; ++use Magento\Framework\Api\ExtensibleDataObjectConverter; ++use Magento\Framework\App\Config\ScopeConfigInterface; ++use Magento\Framework\AuthorizationInterface; ++use Magento\Framework\DataObjectFactory as ObjectFactory; ++use Magento\Framework\Encryption\EncryptorInterface as Encryptor; ++use Magento\Framework\Event\ManagerInterface; ++use Magento\Framework\Exception\AuthorizationException; ++use Magento\Framework\Mail\Template\TransportBuilder; ++use Magento\Framework\Math\Random; ++use Magento\Framework\Reflection\DataObjectProcessor; ++use Magento\Framework\Registry; ++use Magento\Framework\Stdlib\DateTime; ++use Magento\Framework\Stdlib\StringUtils as StringHelper; ++use Magento\Store\Model\StoreManagerInterface; ++use Psr\Log\LoggerInterface as PsrLogger; + + /** + * Account Management service implementation for external API access. ++ * + * Handle various customer account actions. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class AccountManagementApi extends AccountManagement + { ++ /** ++ * @var AuthorizationInterface ++ */ ++ private $authorization; ++ ++ /** ++ * @param CustomerFactory $customerFactory ++ * @param ManagerInterface $eventManager ++ * @param StoreManagerInterface $storeManager ++ * @param Random $mathRandom ++ * @param Validator $validator ++ * @param ValidationResultsInterfaceFactory $validationResultsDataFactory ++ * @param AddressRepositoryInterface $addressRepository ++ * @param CustomerMetadataInterface $customerMetadataService ++ * @param CustomerRegistry $customerRegistry ++ * @param PsrLogger $logger ++ * @param Encryptor $encryptor ++ * @param ConfigShare $configShare ++ * @param StringHelper $stringHelper ++ * @param CustomerRepositoryInterface $customerRepository ++ * @param ScopeConfigInterface $scopeConfig ++ * @param TransportBuilder $transportBuilder ++ * @param DataObjectProcessor $dataProcessor ++ * @param Registry $registry ++ * @param CustomerViewHelper $customerViewHelper ++ * @param DateTime $dateTime ++ * @param CustomerModel $customerModel ++ * @param ObjectFactory $objectFactory ++ * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter ++ * @param AuthorizationInterface $authorization ++ * @SuppressWarnings(PHPMD.ExcessiveParameterList) ++ */ ++ public function __construct( ++ CustomerFactory $customerFactory, ++ ManagerInterface $eventManager, ++ StoreManagerInterface $storeManager, ++ Random $mathRandom, ++ Validator $validator, ++ ValidationResultsInterfaceFactory $validationResultsDataFactory, ++ AddressRepositoryInterface $addressRepository, ++ CustomerMetadataInterface $customerMetadataService, ++ CustomerRegistry $customerRegistry, ++ PsrLogger $logger, ++ Encryptor $encryptor, ++ ConfigShare $configShare, ++ StringHelper $stringHelper, ++ CustomerRepositoryInterface $customerRepository, ++ ScopeConfigInterface $scopeConfig, ++ TransportBuilder $transportBuilder, ++ DataObjectProcessor $dataProcessor, ++ Registry $registry, ++ CustomerViewHelper $customerViewHelper, ++ DateTime $dateTime, ++ CustomerModel $customerModel, ++ ObjectFactory $objectFactory, ++ ExtensibleDataObjectConverter $extensibleDataObjectConverter, ++ AuthorizationInterface $authorization ++ ) { ++ $this->authorization = $authorization; ++ parent::__construct( ++ $customerFactory, ++ $eventManager, ++ $storeManager, ++ $mathRandom, ++ $validator, ++ $validationResultsDataFactory, ++ $addressRepository, ++ $customerMetadataService, ++ $customerRegistry, ++ $logger, ++ $encryptor, ++ $configShare, ++ $stringHelper, ++ $customerRepository, ++ $scopeConfig, ++ $transportBuilder, ++ $dataProcessor, ++ $registry, ++ $customerViewHelper, ++ $dateTime, ++ $customerModel, ++ $objectFactory, ++ $extensibleDataObjectConverter ++ ); ++ } ++ + /** + * @inheritDoc + * +@@ -23,9 +134,30 @@ class AccountManagementApi extends AccountManagement + */ + public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') + { ++ $this->validateCustomerRequest($customer); + $customer = parent::createAccount($customer, $password, $redirectUrl); + $customer->setConfirmation(null); + + return $customer; + } ++ ++ /** ++ * Validate anonymous request ++ * ++ * @param CustomerInterface $customer ++ * @return void ++ * @throws AuthorizationException ++ */ ++ private function validateCustomerRequest(CustomerInterface $customer): void ++ { ++ $groupId = $customer->getGroupId(); ++ if (isset($groupId) && ++ !$this->authorization->isAllowed(self::ADMIN_RESOURCE) ++ ) { ++ $params = ['resources' => self::ADMIN_RESOURCE]; ++ throw new AuthorizationException( ++ __("The consumer isn't authorized to access %resources.", $params) ++ ); ++ } ++ } + } +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +new file mode 100644 +index 0000000000000..5b5c8ce1fc0ca +--- /dev/null ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -0,0 +1,78 @@ ++authorization = $authorization; ++ } ++ ++ /** ++ * Validate groupId for anonymous request ++ * ++ * @param MassSchedule $massSchedule ++ * @param string $topic ++ * @param array $entitiesArray ++ * @param string|null $groupId ++ * @param string|null $userId ++ * @return null ++ * @throws AuthorizationException ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function beforePublishMass( ++ MassSchedule $massSchedule, ++ string $topic, ++ array $entitiesArray, ++ string $groupId = null, ++ string $userId = null ++ ) { ++ foreach ($entitiesArray as $entityParams) { ++ foreach ($entityParams as $entity) { ++ if ($entity instanceof CustomerInterface) { ++ $groupId = $entity->getGroupId(); ++ if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { ++ $params = ['resources' => self::ADMIN_RESOURCE]; ++ throw new AuthorizationException( ++ __("The consumer isn't authorized to access %resources.", $params) ++ ); ++ } ++ } ++ } ++ } ++ return null; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +new file mode 100644 +index 0000000000000..074d40021a184 +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +@@ -0,0 +1,421 @@ ++customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); ++ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); ++ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); ++ $this->random = $this->createMock(Random::class); ++ $this->validator = $this->createMock(Validator::class); ++ $this->validationResultsInterfaceFactory = $this->createMock( ++ ValidationResultsInterfaceFactory::class ++ ); ++ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); ++ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); ++ $this->customerRegistry = $this->createMock(CustomerRegistry::class); ++ ++ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); ++ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); ++ $this->share = $this->createMock(Share::class); ++ $this->string = $this->createMock(StringUtils::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->transportBuilder = $this->createMock(TransportBuilder::class); ++ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); ++ $this->registry = $this->createMock(Registry::class); ++ $this->customerViewHelper = $this->createMock(View::class); ++ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); ++ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); ++ $this->objectFactory = $this->createMock(DataObjectFactory::class); ++ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); ++ $this->extensibleDataObjectConverter = $this->createMock( ++ ExtensibleDataObjectConverter::class ++ ); ++ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); ++ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) ++ ->onlyMethods(['addData', 'setData']) ++ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); ++ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); ++ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); ++ ++ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) ++ ->disableOriginalConstructor() ++ ->onlyMethods(['create']) ++ ->getMock(); ++ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->authorizationMock = $this->createMock(Authorization::class); ++ $this->objectManagerHelper = new ObjectManagerHelper($this); ++ $this->accountManagement = $this->objectManagerHelper->getObject( ++ AccountManagementApi::class, ++ [ ++ 'customerFactory' => $this->customerFactory, ++ 'eventManager' => $this->manager, ++ 'storeManager' => $this->storeManager, ++ 'mathRandom' => $this->random, ++ 'validator' => $this->validator, ++ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, ++ 'addressRepository' => $this->addressRepository, ++ 'customerMetadataService' => $this->customerMetadata, ++ 'customerRegistry' => $this->customerRegistry, ++ 'logger' => $this->logger, ++ 'encryptor' => $this->encryptor, ++ 'configShare' => $this->share, ++ 'stringHelper' => $this->string, ++ 'customerRepository' => $this->customerRepository, ++ 'scopeConfig' => $this->scopeConfig, ++ 'transportBuilder' => $this->transportBuilder, ++ 'dataProcessor' => $this->dataObjectProcessor, ++ 'registry' => $this->registry, ++ 'customerViewHelper' => $this->customerViewHelper, ++ 'dateTime' => $this->dateTime, ++ 'customerModel' => $this->customer, ++ 'objectFactory' => $this->objectFactory, ++ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, ++ 'dateTimeFactory' => $this->dateTimeFactory, ++ 'accountConfirmation' => $this->accountConfirmation, ++ 'sessionManager' => $this->sessionManager, ++ 'saveHandler' => $this->saveHandler, ++ 'visitorCollectionFactory' => $this->visitorCollectionFactory, ++ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, ++ 'addressRegistry' => $this->addressRegistryMock, ++ 'allowedCountriesReader' => $this->allowedCountriesReader, ++ 'authorization' => $this->authorizationMock ++ ] ++ ); ++ $this->accountManagementMock = $this->createMock(AccountManagement::class); ++ ++ $this->storeMock = $this->getMockBuilder( ++ StoreInterface::class ++ )->disableOriginalConstructor() ++ ->getMock(); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @throws LocalizedException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforeCreateAccount( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ ++ $customer = $this->getMockBuilder(CustomerInterface::class) ++ ->addMethods(['setData']) ++ ->getMockForAbstractClass(); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $customer->method('getWebsiteId')->willReturn(2); ++ $customer->method('getStoreId')->willReturn(1); ++ $customer->method('setData')->willReturn(1); ++ ++ $this->customerRepository->method('get')->willReturn($customer); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $this->customerRepository->method('save')->willReturn($customer); ++ ++ if (!$willThrowException) { ++ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); ++ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); ++ $this->random->method('getUniqueHash')->willReturn('testabc'); ++ $date = $this->getMockBuilder(\DateTime::class) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory->expects(static::once()) ++ ->method('create') ++ ->willReturn($date); ++ $date->expects(static::once()) ++ ->method('format') ++ ->with('Y-m-d H:i:s') ++ ->willReturn('2015-01-01 00:00:00'); ++ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); ++ $this->storeManager->method('getStores') ++ ->willReturn([$this->storeMock]); ++ } ++ $this->accountManagement->createAccount($customer); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +index 8ff6a8585212f..cbe0a18e4b178 100644 +--- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void + $minPasswordLength = 5; + $minCharacterSetsNum = 2; + $defaultGroupId = 1; +- $requestedGroupId = 3; + + $datetime = $this->prepareDateTimeFactory(); + +@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void + return null; + } + })); +- $customer->expects($this->atLeastOnce()) +- ->method('getGroupId') +- ->willReturn($requestedGroupId); + $customer + ->method('setGroupId') + ->willReturnOnConsecutiveCalls(null, $defaultGroupId); +diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +new file mode 100644 +index 0000000000000..107df2c2863ef +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +@@ -0,0 +1,112 @@ ++authorizationMock = $this->createMock(Authorization::class); ++ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ ++ 'authorization' => $this->authorizationMock ++ ]); ++ $this->massScheduleMock = $this->createMock(MassSchedule::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforePublishMass( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $customer = $this->getMockForAbstractClass(CustomerInterface::class); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $entitiesArray = [ ++ [$customer, 'Password1', ''] ++ ]; ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ $this->plugin->beforePublishMass( ++ $this->massScheduleMock, ++ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', ++ $entitiesArray, ++ '', ++ '' ++ ); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json +index 2d76da56bff7d..ff34d423c2da5 100644 +--- a/vendor/magento/module-customer/composer.json ++++ b/vendor/magento/module-customer/composer.json +@@ -35,5 +35,6 @@ + "suggest": { + "magento/module-cookie": "100.4.*", + "magento/module-customer-sample-data": "Sample Data version: 100.4.*", +- "magento/module-webapi": "100.4.*" ++ "magento/module-webapi": "100.4.*", ++ "magento/module-asynchronous-operations": "100.4.*" + }, + +diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml +index 31b79935ad9ab..4cda16e121c97 100644 +--- a/vendor/magento/module-customer/etc/di.xml ++++ b/vendor/magento/module-customer/etc/di.xml +@@ -567,4 +567,9 @@ + + + ++ ++ ++ + + +--- + ...AsyncRequestCustomerGroupAuthorization.php | 6 +- + app/code/Magento/Quote/etc/webapi.xml | 3 + + .../Rest/Asynchronous/InputParamsResolver.php | 96 ++++++++++++++++++- + .../Quote/Api/GuestCartManagementTest.php | 2 +- + 4 files changed, 102 insertions(+), 5 deletions(-) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index 5b5c8ce1fc0ca..0aa2b8bfb1d18 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -9,7 +9,6 @@ + namespace Magento\Customer\Plugin; + + use Magento\Customer\Api\Data\CustomerInterface; +-use Magento\Framework\App\ObjectManager; + use Magento\Framework\AuthorizationInterface; + use Magento\Framework\Exception\AuthorizationException; + use Magento\AsynchronousOperations\Model\MassSchedule; +@@ -60,6 +59,11 @@ public function beforePublishMass( + string $groupId = null, + string $userId = null + ) { ++ // only apply the plugin on account create. ++ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ return; ++ } ++ + foreach ($entitiesArray as $entityParams) { + foreach ($entityParams as $entity) { + if ($entity instanceof CustomerInterface) { +diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml +index 79d98968ea198..a7cce5b03a26d 100644 +--- a/vendor/magento/module-quote/etc/webapi.xml ++++ b/vendor/magento/module-quote/etc/webapi.xml +@@ -98,6 +98,9 @@ + + + ++ ++ %customer_id% ++ + + + +diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +index 6718087888bc5..93555559ac9a1 100644 +--- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php ++++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +@@ -8,10 +8,12 @@ + + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; + ++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\Rest\Request as RestRequest; + use Magento\Framework\Webapi\ServiceInputProcessor; +@@ -24,6 +26,8 @@ + + /** + * This class is responsible for retrieving resolved input data ++ * ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class InputParamsResolver + { +@@ -61,6 +65,11 @@ class InputParamsResolver + */ + private $inputArraySizeLimitValue; + ++ /** ++ * @var MethodsMap ++ */ ++ private $methodsMap; ++ + /** + * Initialize dependencies. + * +@@ -72,6 +81,7 @@ class InputParamsResolver + * @param WebapiInputParamsResolver $inputParamsResolver + * @param bool $isBulk + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue ++ * @param MethodsMap|null $methodsMap + */ + public function __construct( + RestRequest $request, +@@ -81,7 +91,8 @@ public function __construct( + RequestValidator $requestValidator, + WebapiInputParamsResolver $inputParamsResolver, + bool $isBulk = false, +- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ++ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, ++ ?MethodsMap $methodsMap = null + ) { + $this->request = $request; + $this->paramsOverrider = $paramsOverrider; +@@ -92,6 +103,8 @@ public function __construct( + $this->isBulk = $isBulk; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); ++ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() ++ ->get(MethodsMap::class); + } + + /** +@@ -119,7 +132,13 @@ public function resolve() + $routeServiceMethod = $route->getServiceMethod(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); + ++ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); ++ + foreach ($inputData as $key => $singleEntityParams) { ++ if (!is_array($singleEntityParams)) { ++ continue; ++ } ++ + $webapiResolvedParams[$key] = $this->resolveBulkItemParams( + $singleEntityParams, + $routeServiceClass, +@@ -143,11 +162,22 @@ public function getInputData() + $inputData = $this->request->getRequestData(); + + $httpMethod = $this->request->getHttpMethod(); +- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { ++ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { + $requestBodyParams = $this->request->getBodyParams(); + $inputData = array_merge($requestBodyParams, $inputData); + } +- return $inputData; ++ ++ return array_map(function ($singleEntityParams) { ++ if (is_array($singleEntityParams)) { ++ $singleEntityParams = $this->filterInputData($singleEntityParams); ++ $singleEntityParams = $this->paramsOverrider->override( ++ $singleEntityParams, ++ $this->getRoute()->getParameters() ++ ); ++ } ++ ++ return $singleEntityParams; ++ }, $inputData); + } + + /** +@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s + { + return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); + } ++ ++ /** ++ * Validates InputData ++ * ++ * @param array $inputData ++ * @return array ++ */ ++ private function filterInputData(array $inputData): array ++ { ++ $result = []; ++ ++ $data = array_filter($inputData, function ($k) use (&$result) { ++ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; ++ return !isset($result[$key]) && ($result[$key] = true); ++ }, ARRAY_FILTER_USE_KEY); ++ ++ return array_map(function ($value) { ++ return is_array($value) ? $this->filterInputData($value) : $value; ++ }, $data); ++ } ++ ++ /** ++ * Validate that parameters are really used in the current request. ++ * ++ * @param string $serviceClassName ++ * @param string $serviceMethodName ++ * @param array $paramOverriders ++ */ ++ private function validateParameters( ++ string $serviceClassName, ++ string $serviceMethodName, ++ array $paramOverriders ++ ): void { ++ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); ++ foreach ($paramOverriders as $key => $param) { ++ $arrayKeys = explode('.', $param ?? ''); ++ $value = array_shift($arrayKeys); ++ ++ foreach ($methodParams as $serviceMethodParam) { ++ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; ++ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; ++ ++ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); ++ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { ++ if (count($arrayKeys) > 0) { ++ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); ++ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); ++ } ++ unset($paramOverriders[$key]); ++ break; ++ } ++ } ++ } ++ ++ if (!empty($paramOverriders)) { ++ $message = 'The current request does not expect the next parameters: ' ++ . implode(', ', $paramOverriders); ++ throw new \UnexpectedValueException(__($message)->__toString()); ++ } ++ } + } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index ce9e4ee941785..44533303c632d 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -339,7 +339,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); + +--- + .../Magento/Quote/Api/GuestCartManagementTest.php | 9 ++++++--- + .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 3 ++- + 2 files changed, 8 insertions(+), 4 deletions(-) + +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index 44533303c632d..e08fe0388cfbe 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -10,10 +10,13 @@ + + class GuestCartManagementTest extends WebapiAbstract + { +- const SERVICE_VERSION = 'V1'; +- const SERVICE_NAME = 'quoteGuestCartManagementV1'; +- const RESOURCE_PATH = '/V1/guest-carts/'; ++ public const SERVICE_VERSION = 'V1'; ++ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; ++ public const RESOURCE_PATH = '/V1/guest-carts/'; + ++ /** ++ * @var array List of created quotes ++ */ + protected $createdQuotes = []; + + /** +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index 9991dd4e05fe7..dc98670bd000f 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -110,4 +110,5 @@ app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php + app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php + app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml + app/code/Magento/GoogleGtag +-app/code/Magento/AdminAdobeIms/Observer/AuthObserver +\ No newline at end of file ++app/code/Magento/AdminAdobeIms/Observer/AuthObserver ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php + +--- + .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index 0aa2b8bfb1d18..295b33d2db14a 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -25,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + ++ /** ++ * account create topic name ++ * ++ * @var string ++ */ ++ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; ++ + /** + * @var AuthorizationInterface + */ +@@ -60,7 +67,7 @@ public function beforePublishMass( + string $userId = null + ) { + // only apply the plugin on account create. +- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ if ($topic !== self::TOPIC_NAME) { + return; + } + diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch new file mode 100644 index 0000000..c78eefe --- /dev/null +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch @@ -0,0 +1,1161 @@ + vendor/magento/module-quote/etc/webapi.xml | 3 + + .../Rest/Asynchronous/InputParamsResolver.php | 96 ++++++++++++++++++- + .../Quote/Api/GuestCartManagementTest.php | 2 +- + 3 files changed, 97 insertions(+), 4 deletions(-) + +diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml +index 79d98968ea198..a7cce5b03a26d 100644 +--- a/vendor/magento/module-quote/etc/webapi.xml ++++ b/vendor/magento/module-quote/etc/webapi.xml +@@ -98,6 +98,9 @@ + + + ++ ++ %customer_id% ++ + + + +diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +index 6718087888bc5..93555559ac9a1 100644 +--- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php ++++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +@@ -8,10 +8,12 @@ + + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; + ++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\Rest\Request as RestRequest; + use Magento\Framework\Webapi\ServiceInputProcessor; +@@ -24,6 +26,8 @@ + + /** + * This class is responsible for retrieving resolved input data ++ * ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class InputParamsResolver + { +@@ -61,6 +65,11 @@ class InputParamsResolver + */ + private $inputArraySizeLimitValue; + ++ /** ++ * @var MethodsMap ++ */ ++ private $methodsMap; ++ + /** + * Initialize dependencies. + * +@@ -72,6 +81,7 @@ class InputParamsResolver + * @param WebapiInputParamsResolver $inputParamsResolver + * @param bool $isBulk + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue ++ * @param MethodsMap|null $methodsMap + */ + public function __construct( + RestRequest $request, +@@ -81,7 +91,8 @@ public function __construct( + RequestValidator $requestValidator, + WebapiInputParamsResolver $inputParamsResolver, + bool $isBulk = false, +- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ++ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, ++ ?MethodsMap $methodsMap = null + ) { + $this->request = $request; + $this->paramsOverrider = $paramsOverrider; +@@ -92,6 +103,8 @@ public function __construct( + $this->isBulk = $isBulk; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); ++ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() ++ ->get(MethodsMap::class); + } + + /** +@@ -119,7 +132,13 @@ public function resolve() + $routeServiceMethod = $route->getServiceMethod(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); + ++ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); ++ + foreach ($inputData as $key => $singleEntityParams) { ++ if (!is_array($singleEntityParams)) { ++ continue; ++ } ++ + $webapiResolvedParams[$key] = $this->resolveBulkItemParams( + $singleEntityParams, + $routeServiceClass, +@@ -143,11 +162,22 @@ public function getInputData() + $inputData = $this->request->getRequestData(); + + $httpMethod = $this->request->getHttpMethod(); +- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { ++ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { + $requestBodyParams = $this->request->getBodyParams(); + $inputData = array_merge($requestBodyParams, $inputData); + } +- return $inputData; ++ ++ return array_map(function ($singleEntityParams) { ++ if (is_array($singleEntityParams)) { ++ $singleEntityParams = $this->filterInputData($singleEntityParams); ++ $singleEntityParams = $this->paramsOverrider->override( ++ $singleEntityParams, ++ $this->getRoute()->getParameters() ++ ); ++ } ++ ++ return $singleEntityParams; ++ }, $inputData); + } + + /** +@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s + { + return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); + } ++ ++ /** ++ * Validates InputData ++ * ++ * @param array $inputData ++ * @return array ++ */ ++ private function filterInputData(array $inputData): array ++ { ++ $result = []; ++ ++ $data = array_filter($inputData, function ($k) use (&$result) { ++ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; ++ return !isset($result[$key]) && ($result[$key] = true); ++ }, ARRAY_FILTER_USE_KEY); ++ ++ return array_map(function ($value) { ++ return is_array($value) ? $this->filterInputData($value) : $value; ++ }, $data); ++ } ++ ++ /** ++ * Validate that parameters are really used in the current request. ++ * ++ * @param string $serviceClassName ++ * @param string $serviceMethodName ++ * @param array $paramOverriders ++ */ ++ private function validateParameters( ++ string $serviceClassName, ++ string $serviceMethodName, ++ array $paramOverriders ++ ): void { ++ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); ++ foreach ($paramOverriders as $key => $param) { ++ $arrayKeys = explode('.', $param ?? ''); ++ $value = array_shift($arrayKeys); ++ ++ foreach ($methodParams as $serviceMethodParam) { ++ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; ++ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; ++ ++ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); ++ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { ++ if (count($arrayKeys) > 0) { ++ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); ++ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); ++ } ++ unset($paramOverriders[$key]); ++ break; ++ } ++ } ++ } ++ ++ if (!empty($paramOverriders)) { ++ $message = 'The current request does not expect the next parameters: ' ++ . implode(', ', $paramOverriders); ++ throw new \UnexpectedValueException(__($message)->__toString()); ++ } ++ } + } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index ce9e4ee941785..44533303c632d 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -339,7 +339,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); + +--- + .../Customer/Model/AccountManagement.php | 5 - + .../Customer/Model/AccountManagementApi.php | 132 ++++++ + ...AsyncRequestCustomerGroupAuthorization.php | 78 ++++ + .../Unit/Model/AccountManagementApiTest.php | 421 ++++++++++++++++++ + .../Test/Unit/Model/AccountManagementTest.php | 4 - + ...cRequestCustomerGroupAuthorizationTest.php | 112 +++++ + app/code/Magento/Customer/composer.json | 3 +- + app/code/Magento/Customer/etc/di.xml | 5 + + 8 files changed, 750 insertions(+), 10 deletions(-) + create mode 100644 app/code/Magento/Customer/Plugin/AsyncRequestCustomerGroupAuthorization.php + create mode 100644 app/code/Magento/Customer/Test/Unit/Model/AccountManagementApiTest.php + create mode 100644 app/code/Magento/Customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php + +diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php +index 702b6aeb68653..bfebafeb0330b 100644 +--- a/vendor/magento/module-customer/Model/AccountManagement.php ++++ b/vendor/magento/module-customer/Model/AccountManagement.php +@@ -882,11 +882,6 @@ public function getConfirmationStatus($customerId) + */ + public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') + { +- $groupId = $customer->getGroupId(); +- if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { +- $customer->setGroupId(null); +- } +- + if ($password !== null) { + $this->checkPasswordStrength($password); + $customerEmail = $customer->getEmail(); +diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php +index 02a05705b57ef..8b4f78ab26c77 100644 +--- a/vendor/magento/module-customer/Model/AccountManagementApi.php ++++ b/vendor/magento/module-customer/Model/AccountManagementApi.php +@@ -6,16 +6,127 @@ + + namespace Magento\Customer\Model; + ++use Magento\Customer\Api\AddressRepositoryInterface; ++use Magento\Customer\Api\CustomerMetadataInterface; ++use Magento\Customer\Api\CustomerRepositoryInterface; + use Magento\Customer\Api\Data\CustomerInterface; ++use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory; ++use Magento\Customer\Helper\View as CustomerViewHelper; ++use Magento\Customer\Model\Config\Share as ConfigShare; ++use Magento\Customer\Model\Customer as CustomerModel; ++use Magento\Customer\Model\Metadata\Validator; ++use Magento\Framework\Api\ExtensibleDataObjectConverter; ++use Magento\Framework\App\Config\ScopeConfigInterface; ++use Magento\Framework\AuthorizationInterface; ++use Magento\Framework\DataObjectFactory as ObjectFactory; ++use Magento\Framework\Encryption\EncryptorInterface as Encryptor; ++use Magento\Framework\Event\ManagerInterface; ++use Magento\Framework\Exception\AuthorizationException; ++use Magento\Framework\Mail\Template\TransportBuilder; ++use Magento\Framework\Math\Random; ++use Magento\Framework\Reflection\DataObjectProcessor; ++use Magento\Framework\Registry; ++use Magento\Framework\Stdlib\DateTime; ++use Magento\Framework\Stdlib\StringUtils as StringHelper; ++use Magento\Store\Model\StoreManagerInterface; ++use Psr\Log\LoggerInterface as PsrLogger; + + /** + * Account Management service implementation for external API access. ++ * + * Handle various customer account actions. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class AccountManagementApi extends AccountManagement + { ++ /** ++ * @var AuthorizationInterface ++ */ ++ private $authorization; ++ ++ /** ++ * @param CustomerFactory $customerFactory ++ * @param ManagerInterface $eventManager ++ * @param StoreManagerInterface $storeManager ++ * @param Random $mathRandom ++ * @param Validator $validator ++ * @param ValidationResultsInterfaceFactory $validationResultsDataFactory ++ * @param AddressRepositoryInterface $addressRepository ++ * @param CustomerMetadataInterface $customerMetadataService ++ * @param CustomerRegistry $customerRegistry ++ * @param PsrLogger $logger ++ * @param Encryptor $encryptor ++ * @param ConfigShare $configShare ++ * @param StringHelper $stringHelper ++ * @param CustomerRepositoryInterface $customerRepository ++ * @param ScopeConfigInterface $scopeConfig ++ * @param TransportBuilder $transportBuilder ++ * @param DataObjectProcessor $dataProcessor ++ * @param Registry $registry ++ * @param CustomerViewHelper $customerViewHelper ++ * @param DateTime $dateTime ++ * @param CustomerModel $customerModel ++ * @param ObjectFactory $objectFactory ++ * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter ++ * @param AuthorizationInterface $authorization ++ * @SuppressWarnings(PHPMD.ExcessiveParameterList) ++ */ ++ public function __construct( ++ CustomerFactory $customerFactory, ++ ManagerInterface $eventManager, ++ StoreManagerInterface $storeManager, ++ Random $mathRandom, ++ Validator $validator, ++ ValidationResultsInterfaceFactory $validationResultsDataFactory, ++ AddressRepositoryInterface $addressRepository, ++ CustomerMetadataInterface $customerMetadataService, ++ CustomerRegistry $customerRegistry, ++ PsrLogger $logger, ++ Encryptor $encryptor, ++ ConfigShare $configShare, ++ StringHelper $stringHelper, ++ CustomerRepositoryInterface $customerRepository, ++ ScopeConfigInterface $scopeConfig, ++ TransportBuilder $transportBuilder, ++ DataObjectProcessor $dataProcessor, ++ Registry $registry, ++ CustomerViewHelper $customerViewHelper, ++ DateTime $dateTime, ++ CustomerModel $customerModel, ++ ObjectFactory $objectFactory, ++ ExtensibleDataObjectConverter $extensibleDataObjectConverter, ++ AuthorizationInterface $authorization ++ ) { ++ $this->authorization = $authorization; ++ parent::__construct( ++ $customerFactory, ++ $eventManager, ++ $storeManager, ++ $mathRandom, ++ $validator, ++ $validationResultsDataFactory, ++ $addressRepository, ++ $customerMetadataService, ++ $customerRegistry, ++ $logger, ++ $encryptor, ++ $configShare, ++ $stringHelper, ++ $customerRepository, ++ $scopeConfig, ++ $transportBuilder, ++ $dataProcessor, ++ $registry, ++ $customerViewHelper, ++ $dateTime, ++ $customerModel, ++ $objectFactory, ++ $extensibleDataObjectConverter ++ ); ++ } ++ + /** + * @inheritDoc + * +@@ -23,9 +134,30 @@ class AccountManagementApi extends AccountManagement + */ + public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') + { ++ $this->validateCustomerRequest($customer); + $customer = parent::createAccount($customer, $password, $redirectUrl); + $customer->setConfirmation(null); + + return $customer; + } ++ ++ /** ++ * Validate anonymous request ++ * ++ * @param CustomerInterface $customer ++ * @return void ++ * @throws AuthorizationException ++ */ ++ private function validateCustomerRequest(CustomerInterface $customer): void ++ { ++ $groupId = $customer->getGroupId(); ++ if (isset($groupId) && ++ !$this->authorization->isAllowed(self::ADMIN_RESOURCE) ++ ) { ++ $params = ['resources' => self::ADMIN_RESOURCE]; ++ throw new AuthorizationException( ++ __("The consumer isn't authorized to access %resources.", $params) ++ ); ++ } ++ } + } +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +new file mode 100644 +index 0000000000000..5b5c8ce1fc0ca +--- /dev/null ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -0,0 +1,78 @@ ++authorization = $authorization; ++ } ++ ++ /** ++ * Validate groupId for anonymous request ++ * ++ * @param MassSchedule $massSchedule ++ * @param string $topic ++ * @param array $entitiesArray ++ * @param string|null $groupId ++ * @param string|null $userId ++ * @return null ++ * @throws AuthorizationException ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function beforePublishMass( ++ MassSchedule $massSchedule, ++ string $topic, ++ array $entitiesArray, ++ string $groupId = null, ++ string $userId = null ++ ) { ++ foreach ($entitiesArray as $entityParams) { ++ foreach ($entityParams as $entity) { ++ if ($entity instanceof CustomerInterface) { ++ $groupId = $entity->getGroupId(); ++ if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { ++ $params = ['resources' => self::ADMIN_RESOURCE]; ++ throw new AuthorizationException( ++ __("The consumer isn't authorized to access %resources.", $params) ++ ); ++ } ++ } ++ } ++ } ++ return null; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +new file mode 100644 +index 0000000000000..074d40021a184 +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +@@ -0,0 +1,421 @@ ++customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); ++ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); ++ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); ++ $this->random = $this->createMock(Random::class); ++ $this->validator = $this->createMock(Validator::class); ++ $this->validationResultsInterfaceFactory = $this->createMock( ++ ValidationResultsInterfaceFactory::class ++ ); ++ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); ++ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); ++ $this->customerRegistry = $this->createMock(CustomerRegistry::class); ++ ++ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); ++ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); ++ $this->share = $this->createMock(Share::class); ++ $this->string = $this->createMock(StringUtils::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->transportBuilder = $this->createMock(TransportBuilder::class); ++ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); ++ $this->registry = $this->createMock(Registry::class); ++ $this->customerViewHelper = $this->createMock(View::class); ++ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); ++ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); ++ $this->objectFactory = $this->createMock(DataObjectFactory::class); ++ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); ++ $this->extensibleDataObjectConverter = $this->createMock( ++ ExtensibleDataObjectConverter::class ++ ); ++ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); ++ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) ++ ->onlyMethods(['addData', 'setData']) ++ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); ++ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); ++ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); ++ ++ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) ++ ->disableOriginalConstructor() ++ ->onlyMethods(['create']) ++ ->getMock(); ++ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->authorizationMock = $this->createMock(Authorization::class); ++ $this->objectManagerHelper = new ObjectManagerHelper($this); ++ $this->accountManagement = $this->objectManagerHelper->getObject( ++ AccountManagementApi::class, ++ [ ++ 'customerFactory' => $this->customerFactory, ++ 'eventManager' => $this->manager, ++ 'storeManager' => $this->storeManager, ++ 'mathRandom' => $this->random, ++ 'validator' => $this->validator, ++ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, ++ 'addressRepository' => $this->addressRepository, ++ 'customerMetadataService' => $this->customerMetadata, ++ 'customerRegistry' => $this->customerRegistry, ++ 'logger' => $this->logger, ++ 'encryptor' => $this->encryptor, ++ 'configShare' => $this->share, ++ 'stringHelper' => $this->string, ++ 'customerRepository' => $this->customerRepository, ++ 'scopeConfig' => $this->scopeConfig, ++ 'transportBuilder' => $this->transportBuilder, ++ 'dataProcessor' => $this->dataObjectProcessor, ++ 'registry' => $this->registry, ++ 'customerViewHelper' => $this->customerViewHelper, ++ 'dateTime' => $this->dateTime, ++ 'customerModel' => $this->customer, ++ 'objectFactory' => $this->objectFactory, ++ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, ++ 'dateTimeFactory' => $this->dateTimeFactory, ++ 'accountConfirmation' => $this->accountConfirmation, ++ 'sessionManager' => $this->sessionManager, ++ 'saveHandler' => $this->saveHandler, ++ 'visitorCollectionFactory' => $this->visitorCollectionFactory, ++ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, ++ 'addressRegistry' => $this->addressRegistryMock, ++ 'allowedCountriesReader' => $this->allowedCountriesReader, ++ 'authorization' => $this->authorizationMock ++ ] ++ ); ++ $this->accountManagementMock = $this->createMock(AccountManagement::class); ++ ++ $this->storeMock = $this->getMockBuilder( ++ StoreInterface::class ++ )->disableOriginalConstructor() ++ ->getMock(); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @throws LocalizedException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforeCreateAccount( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ ++ $customer = $this->getMockBuilder(CustomerInterface::class) ++ ->addMethods(['setData']) ++ ->getMockForAbstractClass(); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $customer->method('getWebsiteId')->willReturn(2); ++ $customer->method('getStoreId')->willReturn(1); ++ $customer->method('setData')->willReturn(1); ++ ++ $this->customerRepository->method('get')->willReturn($customer); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $this->customerRepository->method('save')->willReturn($customer); ++ ++ if (!$willThrowException) { ++ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); ++ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); ++ $this->random->method('getUniqueHash')->willReturn('testabc'); ++ $date = $this->getMockBuilder(\DateTime::class) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory->expects(static::once()) ++ ->method('create') ++ ->willReturn($date); ++ $date->expects(static::once()) ++ ->method('format') ++ ->with('Y-m-d H:i:s') ++ ->willReturn('2015-01-01 00:00:00'); ++ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); ++ $this->storeManager->method('getStores') ++ ->willReturn([$this->storeMock]); ++ } ++ $this->accountManagement->createAccount($customer); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +index 9e68d53fd5949..e2b507f6fe37d 100644 +--- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void + $minPasswordLength = 5; + $minCharacterSetsNum = 2; + $defaultGroupId = 1; +- $requestedGroupId = 3; + + $datetime = $this->prepareDateTimeFactory(); + +@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void + return null; + } + })); +- $customer->expects($this->atLeastOnce()) +- ->method('getGroupId') +- ->willReturn($requestedGroupId); + $customer + ->method('setGroupId') + ->willReturnOnConsecutiveCalls(null, $defaultGroupId); +diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +new file mode 100644 +index 0000000000000..107df2c2863ef +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +@@ -0,0 +1,112 @@ ++authorizationMock = $this->createMock(Authorization::class); ++ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ ++ 'authorization' => $this->authorizationMock ++ ]); ++ $this->massScheduleMock = $this->createMock(MassSchedule::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforePublishMass( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $customer = $this->getMockForAbstractClass(CustomerInterface::class); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $entitiesArray = [ ++ [$customer, 'Password1', ''] ++ ]; ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ $this->plugin->beforePublishMass( ++ $this->massScheduleMock, ++ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', ++ $entitiesArray, ++ '', ++ '' ++ ); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json +index ef2047644759b..39c82c20f2ec8 100644 +--- a/vendor/magento/module-customer/composer.json ++++ b/vendor/magento/module-customer/composer.json +@@ -29,5 +29,6 @@ + "suggest": { + "magento/module-cookie": "100.4.*", + "magento/module-customer-sample-data": "Sample Data version: 100.4.*", +- "magento/module-webapi": "100.4.*" ++ "magento/module-webapi": "100.4.*", ++ "magento/module-asynchronous-operations": "100.4.*" + }, +diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml +index b178f51f89199..96fd4b86be702 100644 +--- a/vendor/magento/module-customer/etc/di.xml ++++ b/vendor/magento/module-customer/etc/di.xml +@@ -585,4 +585,9 @@ + + + ++ ++ ++ + + +--- + .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index 5b5c8ce1fc0ca..d19c48a52a25a 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -60,6 +60,11 @@ public function beforePublishMass( + string $groupId = null, + string $userId = null + ) { ++ // only apply the plugin on account create. ++ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ return; ++ } ++ + foreach ($entitiesArray as $entityParams) { + foreach ($entityParams as $entity) { + if ($entity instanceof CustomerInterface) { + +--- + .../Magento/Quote/Api/GuestCartManagementTest.php | 9 ++++++--- + .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 1 + + 2 files changed, 7 insertions(+), 3 deletions(-) + +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index 44533303c632d..e08fe0388cfbe 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -10,10 +10,13 @@ + + class GuestCartManagementTest extends WebapiAbstract + { +- const SERVICE_VERSION = 'V1'; +- const SERVICE_NAME = 'quoteGuestCartManagementV1'; +- const RESOURCE_PATH = '/V1/guest-carts/'; ++ public const SERVICE_VERSION = 'V1'; ++ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; ++ public const RESOURCE_PATH = '/V1/guest-carts/'; + ++ /** ++ * @var array List of created quotes ++ */ + protected $createdQuotes = []; + + /** +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index 80fe4ec247a64..50285d7492e86 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed + app/code/Magento/GoogleGtag + app/code/Magento/AdminAdobeIms/Observer/AuthObserver + app/code/Magento/OpenSearch/SearchAdapter/Adapter ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php + +--- + .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index d19c48a52a25a..cdda3016694ca 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -26,6 +26,13 @@ class AsyncRequestCustomerGroupAuthorization + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + ++ /** ++ * account create topic name ++ * ++ * @var string ++ */ ++ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; ++ + /** + * @var AuthorizationInterface + */ +@@ -61,7 +68,7 @@ public function beforePublishMass( + string $userId = null + ) { + // only apply the plugin on account create. +- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ if ($topic !== self::TOPIC_NAME) { + return; + } + diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch new file mode 100644 index 0000000..1ab7c79 --- /dev/null +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch @@ -0,0 +1,286 @@ +--- + ...AsyncRequestCustomerGroupAuthorization.php | 6 +- + app/code/Magento/Quote/etc/webapi.xml | 3 + + .../Rest/Asynchronous/InputParamsResolver.php | 96 ++++++++++++++++++- + .../Quote/Api/GuestCartManagementTest.php | 2 +- + 4 files changed, 102 insertions(+), 5 deletions(-) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index 5b5c8ce1fc0ca..0aa2b8bfb1d18 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -9,7 +9,6 @@ + namespace Magento\Customer\Plugin; + + use Magento\Customer\Api\Data\CustomerInterface; +-use Magento\Framework\App\ObjectManager; + use Magento\Framework\AuthorizationInterface; + use Magento\Framework\Exception\AuthorizationException; + use Magento\AsynchronousOperations\Model\MassSchedule; +@@ -60,6 +59,11 @@ public function beforePublishMass( + string $groupId = null, + string $userId = null + ) { ++ // only apply the plugin on account create. ++ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ return; ++ } ++ + foreach ($entitiesArray as $entityParams) { + foreach ($entityParams as $entity) { + if ($entity instanceof CustomerInterface) { +diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml +index 79d98968ea198..a7cce5b03a26d 100644 +--- a/vendor/magento/module-quote/etc/webapi.xml ++++ b/vendor/magento/module-quote/etc/webapi.xml +@@ -98,6 +98,9 @@ + + + ++ ++ %customer_id% ++ + + + +diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +index 6718087888bc5..93555559ac9a1 100644 +--- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php ++++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +@@ -8,10 +8,12 @@ + + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; + ++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\Rest\Request as RestRequest; + use Magento\Framework\Webapi\ServiceInputProcessor; +@@ -24,6 +26,8 @@ + + /** + * This class is responsible for retrieving resolved input data ++ * ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class InputParamsResolver + { +@@ -61,6 +65,11 @@ class InputParamsResolver + */ + private $inputArraySizeLimitValue; + ++ /** ++ * @var MethodsMap ++ */ ++ private $methodsMap; ++ + /** + * Initialize dependencies. + * +@@ -72,6 +81,7 @@ class InputParamsResolver + * @param WebapiInputParamsResolver $inputParamsResolver + * @param bool $isBulk + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue ++ * @param MethodsMap|null $methodsMap + */ + public function __construct( + RestRequest $request, +@@ -81,7 +91,8 @@ public function __construct( + RequestValidator $requestValidator, + WebapiInputParamsResolver $inputParamsResolver, + bool $isBulk = false, +- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ++ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, ++ ?MethodsMap $methodsMap = null + ) { + $this->request = $request; + $this->paramsOverrider = $paramsOverrider; +@@ -92,6 +103,8 @@ public function __construct( + $this->isBulk = $isBulk; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); ++ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() ++ ->get(MethodsMap::class); + } + + /** +@@ -119,7 +132,13 @@ public function resolve() + $routeServiceMethod = $route->getServiceMethod(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); + ++ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); ++ + foreach ($inputData as $key => $singleEntityParams) { ++ if (!is_array($singleEntityParams)) { ++ continue; ++ } ++ + $webapiResolvedParams[$key] = $this->resolveBulkItemParams( + $singleEntityParams, + $routeServiceClass, +@@ -143,11 +162,22 @@ public function getInputData() + $inputData = $this->request->getRequestData(); + + $httpMethod = $this->request->getHttpMethod(); +- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { ++ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { + $requestBodyParams = $this->request->getBodyParams(); + $inputData = array_merge($requestBodyParams, $inputData); + } +- return $inputData; ++ ++ return array_map(function ($singleEntityParams) { ++ if (is_array($singleEntityParams)) { ++ $singleEntityParams = $this->filterInputData($singleEntityParams); ++ $singleEntityParams = $this->paramsOverrider->override( ++ $singleEntityParams, ++ $this->getRoute()->getParameters() ++ ); ++ } ++ ++ return $singleEntityParams; ++ }, $inputData); + } + + /** +@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s + { + return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); + } ++ ++ /** ++ * Validates InputData ++ * ++ * @param array $inputData ++ * @return array ++ */ ++ private function filterInputData(array $inputData): array ++ { ++ $result = []; ++ ++ $data = array_filter($inputData, function ($k) use (&$result) { ++ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; ++ return !isset($result[$key]) && ($result[$key] = true); ++ }, ARRAY_FILTER_USE_KEY); ++ ++ return array_map(function ($value) { ++ return is_array($value) ? $this->filterInputData($value) : $value; ++ }, $data); ++ } ++ ++ /** ++ * Validate that parameters are really used in the current request. ++ * ++ * @param string $serviceClassName ++ * @param string $serviceMethodName ++ * @param array $paramOverriders ++ */ ++ private function validateParameters( ++ string $serviceClassName, ++ string $serviceMethodName, ++ array $paramOverriders ++ ): void { ++ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); ++ foreach ($paramOverriders as $key => $param) { ++ $arrayKeys = explode('.', $param ?? ''); ++ $value = array_shift($arrayKeys); ++ ++ foreach ($methodParams as $serviceMethodParam) { ++ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; ++ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; ++ ++ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); ++ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { ++ if (count($arrayKeys) > 0) { ++ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); ++ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); ++ } ++ unset($paramOverriders[$key]); ++ break; ++ } ++ } ++ } ++ ++ if (!empty($paramOverriders)) { ++ $message = 'The current request does not expect the next parameters: ' ++ . implode(', ', $paramOverriders); ++ throw new \UnexpectedValueException(__($message)->__toString()); ++ } ++ } + } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index 68cc2c2b2315d..6f08b21f38127 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -354,7 +354,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); + +--- + .../Controller/Rest/Asynchronous/InputParamsResolver.php | 1 + + .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +index 93555559ac9a1..6e159eaddf162 100644 +--- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php ++++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +@@ -243,6 +243,7 @@ private function validateParameters( + string $serviceMethodName, + array $paramOverriders + ): void { ++ //phpcs:ignore CopyPaste + $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); + foreach ($paramOverriders as $key => $param) { + $arrayKeys = explode('.', $param ?? ''); +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index 08ba4bba28c62..c9d07aa2abedf 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed + app/code/Magento/GoogleGtag + app/code/Magento/AdminAdobeIms/Observer/AuthObserver + app/code/Magento/OpenSearch/SearchAdapter/Adapter ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php + +--- + .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +index 0aa2b8bfb1d18..295b33d2db14a 100644 +--- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php ++++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +@@ -25,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + ++ /** ++ * account create topic name ++ * ++ * @var string ++ */ ++ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; ++ + /** + * @var AuthorizationInterface + */ +@@ -60,7 +67,7 @@ public function beforePublishMass( + string $userId = null + ) { + // only apply the plugin on account create. +- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ if ($topic !== self::TOPIC_NAME) { + return; + } + From f579459dce52dac6cc276d3514375bab18f0c573 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Mon, 27 Jan 2025 21:24:09 +0530 Subject: [PATCH 52/82] Add support of php 8.4 to cloud-patches --- .../Acceptance/Acceptance84Cest.php | 24 ++ tests/unit/phpunit.xml.dist | 5 + tests/unit/tmp/junit.xml | 306 ++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 src/Test/Functional/Acceptance/Acceptance84Cest.php create mode 100644 tests/unit/tmp/junit.xml diff --git a/src/Test/Functional/Acceptance/Acceptance84Cest.php b/src/Test/Functional/Acceptance/Acceptance84Cest.php new file mode 100644 index 0000000..fac3002 --- /dev/null +++ b/src/Test/Functional/Acceptance/Acceptance84Cest.php @@ -0,0 +1,24 @@ + '2.4.8', 'magentoVersion' => '2.4.8'], + ]; + } +} diff --git a/tests/unit/phpunit.xml.dist b/tests/unit/phpunit.xml.dist index 1609fe4..9f032a9 100644 --- a/tests/unit/phpunit.xml.dist +++ b/tests/unit/phpunit.xml.dist @@ -11,6 +11,11 @@ ../../src/Test/Unit + + + ../../src + + diff --git a/tests/unit/tmp/junit.xml b/tests/unit/tmp/junit.xml new file mode 100644 index 0000000..4761d47 --- /dev/null +++ b/tests/unit/tmp/junit.xml @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Magento\CloudPatches\Test\Unit\Shell\Command\PatchDriverTest::testApplyFailure +Symfony\Component\Process\Exception\ProcessTimedOutException: The process "'patch' '--silent' '-p1' '--dry-run'" exceeded the timeout of 120 seconds. + +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:1157 +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:424 +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:252 +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:269 +/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:70 +/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:38 +/Users/panney.yadav/Sites/MCLOUD-13149/src/Test/Unit/Shell/Command/PatchDriverTest.php:95 + + + + Magento\CloudPatches\Test\Unit\Shell\Command\PatchDriverTest::testRevertFailure +Symfony\Component\Process\Exception\ProcessTimedOutException: The process "'patch' '--silent' '-p1' '--reverse' '--dry-run'" exceeded the timeout of 60 seconds. + +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:1157 +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:424 +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:252 +/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:269 +/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:83 +/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:52 +/Users/panney.yadav/Sites/MCLOUD-13149/src/Test/Unit/Shell/Command/PatchDriverTest.php:131 + + + + + From ad7af50e7c74250085b8a4dc4c523890f669e73a Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Tue, 28 Jan 2025 11:57:25 +0530 Subject: [PATCH 53/82] MCLOUD-13240 : Patch for CVE-2025-24434 - Improve-web-api-async --- patches.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patches.json b/patches.json index de60636..62a939c 100644 --- a/patches.json +++ b/patches.json @@ -294,10 +294,10 @@ ">=2.4.7 <2.4.7-p2": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch" }, "Patch for CVE-2025-24434 - Improve-web-api-async": { - ">=2.4.4 <2.4.4-p11": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch", - ">=2.4.5 <2.4.5-p10": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch", - ">=2.4.6 <2.4.6-p8": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch", - "2.4.7": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch" + ">=2.4.4 <2.4.4-p12": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch", + ">=2.4.5 <2.4.5-p11": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch", + ">=2.4.6 <2.4.6-p9": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch", + ">=2.4.7 <2.4.7-p5": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch" } }, "magento/module-paypal": { From 0de92914039e3ee351c349a4d5dae1379976a4f9 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 28 Jan 2025 12:12:03 +0530 Subject: [PATCH 54/82] Add support of php 8.4 to cloud-patches --- .../Action/ApplyOptionalActionTest.php | 4 +- .../Action/ConfirmRequiredActionTest.php | 42 +-- .../Process/Action/RevertActionTest.php | 45 ++- .../Action/ReviewAppliedActionTest.php | 19 +- .../Unit/Command/Process/ApplyLocalTest.php | 8 +- .../Command/Process/ApplyOptionalTest.php | 9 +- .../Command/Process/ApplyRequiredTest.php | 44 +-- .../Unit/Command/Process/Ece/RevertTest.php | 6 +- .../Unit/Command/Process/RendererTest.php | 4 +- .../Patch/Collector/CloudCollectorTest.php | 16 +- .../Patch/Collector/LocalCollectorTest.php | 22 +- .../Patch/Collector/QualityCollectorTest.php | 177 +++++----- .../Unit/Patch/Conflict/ProcessorTest.php | 9 +- tests/unit/tmp/junit.xml | 306 ------------------ 14 files changed, 166 insertions(+), 545 deletions(-) delete mode 100644 tests/unit/tmp/junit.xml diff --git a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php index 2801521..4b509f6 100644 --- a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php @@ -124,7 +124,7 @@ public function testExecuteSuccessful() ]); $this->renderer->expects($this->exactly(3)) ->method('printPatchInfo') - ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ @@ -261,7 +261,7 @@ public function testApplyWithException() }); $this->conflictProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ diff --git a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php index f71b022..38ae179 100644 --- a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php @@ -86,9 +86,9 @@ public function testAskConfirmationForNotAppliedPatches() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { @@ -99,7 +99,7 @@ public function testAskConfirmationForNotAppliedPatches() }) ->willReturn([$patch1, $patch2, $patch3]); - $aggregatedPatch = $this->getMockForAbstractClass(AggregatedPatchInterface::class); + $aggregatedPatch = $this->createMock(AggregatedPatchInterface::class); $this->aggregator->expects($this->once()) ->method('aggregate') ->with([$patch1, $patch2]) @@ -108,7 +108,7 @@ public function testAskConfirmationForNotAppliedPatches() $this->renderer->expects($this->once()) ->method('printTable') ->with($outputMock, [$aggregatedPatch]) - ->willReturnCallback(function($output,$patches ) use ($outputMock, $aggregatedPatch) { + ->willReturnCallback(function($output) use ($outputMock, $aggregatedPatch) { if ($output === $outputMock && $aggregatedPatch === [$aggregatedPatch] ) { throw new RuntimeException('Error message'); } @@ -129,17 +129,12 @@ public function testPatchNotFoundException() $patchFilter = ['unknown id']; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); - $this->optionalPool->expects($this->once()) + $outputMock = $this->createMock(OutputInterface::class); + $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->with($patchFilter) ->willThrowException(new PatchNotFoundException('')); $this->expectException(RuntimeException::class); @@ -159,9 +154,9 @@ public function testConfirmationRejected() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { @@ -172,21 +167,16 @@ public function testConfirmationRejected() }) ->willReturn([$patch1]); - $aggregatedPatch = $this->getMockForAbstractClass(AggregatedPatchInterface::class); + $aggregatedPatch = $this->createMock(AggregatedPatchInterface::class); $this->aggregator->expects($this->once()) ->method('aggregate') ->with([$patch1]) ->willReturn([$aggregatedPatch]); - $this->renderer->expects($this->once()) - ->method('printTable') - ->with($outputMock, [$aggregatedPatch]) - ->willReturnCallback(function($output,$patches ) use ($outputMock, $aggregatedPatch) { - if ($output === $outputMock && $aggregatedPatch === [$aggregatedPatch] ) { - throw new RuntimeException('Error message'); - } - return null; - }); + $this->optionalPool->expects($this->once()) + ->method('getAdditionalRequiredPatches') + ->with($patchFilter) + ->willReturn([$patch1]); $this->renderer->expects($this->once()) ->method('printQuestion') @@ -207,7 +197,7 @@ public function testConfirmationRejected() */ private function createPatch(string $path, string $id, bool $isDeprecated = false) { - $patch = $this->getMockForAbstractClass(PatchInterface::class); + $patch = $this->createMock(PatchInterface::class); $patch->method('getPath')->willReturn($path); $patch->method('getFilename')->willReturn('filename.patch'); $patch->method('getId')->willReturn($id); diff --git a/src/Test/Unit/Command/Process/Action/RevertActionTest.php b/src/Test/Unit/Command/Process/Action/RevertActionTest.php index 0c722e1..ac13a40 100644 --- a/src/Test/Unit/Command/Process/Action/RevertActionTest.php +++ b/src/Test/Unit/Command/Process/Action/RevertActionTest.php @@ -105,9 +105,9 @@ public function testExecuteSuccessful() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { @@ -126,7 +126,7 @@ public function testExecuteSuccessful() $this->renderer->expects($this->exactly(2)) ->method('printPatchInfo') - ->willReturnCallback(function() use ($patch1, $patch2) { + ->willReturnCallback(function($patch, $message) use ($patch1, $patch2) { static $callCount = 0; $expectedPatches = [$patch1, $patch2]; $expectedMessages = [ @@ -160,9 +160,9 @@ public function testRevertNotAppliedPatch() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { @@ -181,7 +181,7 @@ public function testRevertNotAppliedPatch() $outputMock->expects($this->once()) ->method('writeln') ->willReturnCallback(function($patchId) use ($patchFilter,$patch1) { - if ($patchId === $expectedMessage && $$patch1 === $patch1->getId()) { + if ($patchId === $patch1->getId()) { $this->stringContains( 'Patch ' . $patch1->getId() . ' (' . $patch1->getFilename() . ') is not applied' ); @@ -203,9 +203,9 @@ public function testRevertWithException() $errorMessage = sprintf('Reverting patch %s (%s) failed.', $patch1->getId(), $patch1->getPath()); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->method('getList') ->willReturn([$patch1]); @@ -232,17 +232,12 @@ public function testPatchNotFoundException() $patchFilter = ['unknown id']; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); - $this->optionalPool->expects($this->once()) + $outputMock = $this->createMock(OutputInterface::class); + $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->with($patchFilter) ->willThrowException(new PatchNotFoundException('')); $this->expectException(RuntimeException::class); @@ -257,19 +252,15 @@ public function testValidationFailedException() $patchFilter = ['MC-11111']; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); - $this->revertValidator->expects($this->once()) + $this->revertValidator->expects($this->once()) ->method('validate') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->with($patchFilter) ->willThrowException(new RuntimeException('Error')); + $this->optionalPool->expects($this->never()) ->method('getList'); @@ -287,7 +278,7 @@ public function testValidationFailedException() */ private function createPatch(string $path, string $id) { - $patch = $this->getMockForAbstractClass(PatchInterface::class); + $patch = $this->createMock(PatchInterface::class); $patch->method('getPath')->willReturn($path); $patch->method('getId')->willReturn($id); diff --git a/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php b/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php index b933550..328383f 100644 --- a/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php @@ -54,7 +54,7 @@ class ReviewAppliedActionTest extends TestCase */ protected function setUp(): void { - $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->statusPool = $this->createMock(StatusPool::class); $this->optionalPool = $this->createMock(OptionalPool::class); @@ -82,9 +82,9 @@ public function testAppliedPatchesExceedsLimit() } /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->statusPool->method('isApplied') ->willReturn(true); @@ -95,12 +95,7 @@ public function testAppliedPatchesExceedsLimit() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function($filter) use ($patch1) { - if ($filter === $patch1) { - $this->stringContains('error'); - } - return false; - }); + ->with($this->stringContains('error')); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -116,9 +111,9 @@ public function testAppliedPatchesNotExceedLimit() } /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->statusPool->method('isApplied') ->willReturn(true); @@ -142,7 +137,7 @@ public function testAppliedPatchesNotExceedLimit() */ private function createPatch(string $id) { - $patch = $this->getMockForAbstractClass(PatchInterface::class); + $patch = $this->createMock(PatchInterface::class); $patch->method('getId')->willReturn($id); return $patch; diff --git a/src/Test/Unit/Command/Process/ApplyLocalTest.php b/src/Test/Unit/Command/Process/ApplyLocalTest.php index 4c3d50d..f97417c 100644 --- a/src/Test/Unit/Command/Process/ApplyLocalTest.php +++ b/src/Test/Unit/Command/Process/ApplyLocalTest.php @@ -62,7 +62,7 @@ class ApplyLocalTest extends TestCase protected function setUp(): void { $this->applier = $this->createMock(Applier::class); - $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->localPool = $this->createMock(LocalPool::class); $this->renderer = $this->createMock(Renderer::class); $this->rollbackProcessor = $this->createMock(RollbackProcessor::class); @@ -86,9 +86,9 @@ public function testExecuteLocalPatchesNotFound() $expectedMessage = 'Hot-fixes were not found. Skipping'; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->localPool->method('getList') ->willReturn([]); $outputMock->expects($this->once()) @@ -125,7 +125,7 @@ public function testApplySuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ diff --git a/src/Test/Unit/Command/Process/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/ApplyOptionalTest.php index 841d923..ef08794 100644 --- a/src/Test/Unit/Command/Process/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/ApplyOptionalTest.php @@ -80,14 +80,9 @@ public function testApplyWithPatchArgumentProvided() ->with($cliPatchArgument) ->willReturn($cliPatchArgument); - $this->actionPool->expects($this->once()) + $this->actionPool->expects($this->once()) ->method('execute') - ->willReturnCallback(function($input, $output, $cliPatch) use ($inputputMock, $outputMock, $patch) { - if ($input === $inputputMock && $output === $outputMock && $patches === $cliPatch) { - return true; - } - return false; - }); + ->with($inputMock, $outputMock, $cliPatchArgument); $this->applyOptional->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index ad0cf30..c4478d8 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -62,7 +62,7 @@ class ApplyRequiredTest extends TestCase protected function setUp(): void { $this->applier = $this->createMock(Applier::class); - $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->requiredPool = $this->createMock(RequiredPool::class); $this->renderer = $this->createMock(Renderer::class); $this->conflictProcessor = $this->createMock(ConflictProcessor::class); @@ -88,9 +88,9 @@ public function testApplySuccessful() $patch3 = $this->createPatch('/path/patch3.patch', 'MC-33333'); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->requiredPool->method('getList') ->willReturn([$patch1, $patch2, $patch3]); @@ -103,7 +103,7 @@ public function testApplySuccessful() $this->renderer->expects($this->exactly(3)) ->method('printPatchInfo') - ->willReturnCallback(function() use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ @@ -132,39 +132,29 @@ public function testApplyWithException() $patch = $this->createPatch('/path/patch.patch', 'MC-11111'); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->getMockForAbstractClass(InputInterface::class); + $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); $this->requiredPool->method('getList') ->willReturn([$patch]); $this->applier->method('apply') - ->willReturnCallback(function ($path, $id) use ($patch) { - - $this->assertSame($path, $patch->getPath()); - $this->assertSame($id, $patch->getId()); - throw new ApplierException('Applier error message'); // Throw ApplierException directly here - }); + ->with( + $this->logicalOr($this->equalTo($patch->getPath()), $this->equalTo($patch->getId())) + ) + ->willThrowException(new ApplierException('Applier error message')); $this->conflictProcessor->expects($this->once()) - ->method('process') - ->with($outputMock, $patch, [], 'Applier error message') - ->willReturnCallback(function($output, $patch, $data, string $errorMessage) use ($outputMock, $patch2, $patch1) { - if ($output === $outputMock && $patch === $patch2 && $data === $patch1 && $errorMessage === 'Applier error message') { - throw new RuntimeException('Error message'); - } - return null; - }); - - $this->conflictProcessor->expects($this->once()) - ->method('process') - ->with($outputMock, $patch, [], 'Applier error message') - ->willThrowException(new RuntimeException('Error message')); + ->method('process') + ->with( + $this->logicalOr($this->equalTo($outputMock), $this->equalTo($patch), $this->equalTo([]), $this->equalTo('Applier error message')) + ) + ->willThrowException(new RuntimeException('Error message')); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); - $this->manager->run($inputMock, $outputMock, $patchFilter); + $this->manager->run($inputMock, $outputMock); } /** @@ -177,7 +167,7 @@ public function testApplyWithException() */ private function createPatch(string $path, string $id) { - $patch = $this->getMockForAbstractClass(PatchInterface::class); + $patch = $this->createMock(PatchInterface::class); $patch->method('getPath')->willReturn($path); $patch->method('getId')->willReturn($id); diff --git a/src/Test/Unit/Command/Process/Ece/RevertTest.php b/src/Test/Unit/Command/Process/Ece/RevertTest.php index c9c539b..69c178c 100644 --- a/src/Test/Unit/Command/Process/Ece/RevertTest.php +++ b/src/Test/Unit/Command/Process/Ece/RevertTest.php @@ -116,7 +116,7 @@ public function testRevertSuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function() use ($patch1, $patch2) { + ->willReturnCallback(function($patch, $message) use ($patch1, $patch2) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ @@ -182,9 +182,9 @@ function ($path, $title) { $this->revertAction->expects($this->once()) ->method('execute') - ->with($inputMock, $outputMock, []) + ->with($inputMock, $outputMock) ->willReturnCallback(function($input, $output) use ($inputMock, $outputMock) { - if ($output === $outputMock && $input === $inputMock && $patch === [] ) { + if ($output === $outputMock && $input === $inputMock) { return true; } return false; diff --git a/src/Test/Unit/Command/Process/RendererTest.php b/src/Test/Unit/Command/Process/RendererTest.php index facf535..992e997 100644 --- a/src/Test/Unit/Command/Process/RendererTest.php +++ b/src/Test/Unit/Command/Process/RendererTest.php @@ -76,9 +76,9 @@ public function testPrintPatchInfo(PatchInterface $patch, string $prependedMessa $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $outputMock->expects($this->atLeastOnce()) ->method('writeln') - ->willReturnCallback(function($filter) use ($expectedArray, $patch1) { + ->willReturnCallback(function($filter) use ($expectedArray) { if ($filter === $expectedArray) { - return [$patch1]; + return $expectedArray; } return []; }); diff --git a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php index 585bfc4..850e0be 100644 --- a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php @@ -148,17 +148,11 @@ public function testCollectSuccessful(bool $isCloud, string $expectedType) $this->patchBuilder->expects($this->exactly(3)) ->method('setType') - ->willReturnCallback(function() use ($expectedType1, $expectedType2, $expectedType3) { - static $callCount = 0; - $expectedType = [$expectedType1, $expectedType2, $expectedType3]; - - if ($patch === $expectedType[$callCount]) { - $callCount++; - return true; - } - - return false; - }); + ->with($this->logicalOr( + $this->equalTo($expectedType), + $this->equalTo($expectedType), + $this->equalTo($expectedType) + )); $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageName') ->willReturnCallback(function ($args) { diff --git a/src/Test/Unit/Patch/Collector/LocalCollectorTest.php b/src/Test/Unit/Patch/Collector/LocalCollectorTest.php index 5a614a1..8fdfcdf 100644 --- a/src/Test/Unit/Patch/Collector/LocalCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/LocalCollectorTest.php @@ -65,24 +65,14 @@ public function testCollect() $this->patchBuilder->expects($this->exactly(2)) ->method('setId') - ->willReturnCallback(function () use (&$callCount, $shortPath1, $shortPath2) { - $callCount++; - if ($callCount === 1) { - return $shortPath1; - } elseif ($callCount === 2) { - return $shortPath12; - } - }); + ->with( + $this->logicalOr($this->equalTo($shortPath1), $this->equalTo($shortPath2)) + ); $this->patchBuilder->expects($this->exactly(2)) ->method('setTitle') - ->willReturnCallback(function () use (&$callCount, $shortPath1, $shortPath2) { - $callCount++; - if ($callCount === 1) { - return $shortPath1; - } elseif ($callCount === 2) { - return $shortPath12; - } - }); + ->with( + $this->logicalOr($this->equalTo($shortPath1), $this->equalTo($shortPath2)) + ); $this->patchBuilder->expects($this->exactly(2)) ->method('setFilename') diff --git a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php index 3b93234..f3a1173 100644 --- a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php @@ -86,13 +86,6 @@ public function testCollectSuccessful() $this->qualityPackage->method('getPatchesDirectoryPath') ->willReturn(self::QUALITY_PATCH_DIR); - $this->package->method('matchConstraint') - ->willReturnMap([ - ['magento/magento2-base', '2.1.4 - 2.1.14', false], - ['magento/magento2-base', '2.2.0 - 2.2.5', true], - ['magento/magento2-ee-base', '2.2.0 - 2.2.5', true], - ]); - $this->package->method('matchConstraint') ->willReturnMap([ ['magento/magento2-base', '2.1.4 - 2.1.14', false], @@ -100,114 +93,107 @@ public function testCollectSuccessful() ['magento/magento2-ee-base', '2.2.0 - 2.2.5', true], ]); + // Replacing withConsecutive with with() and logicalOr $this->patchBuilder->expects($this->exactly(3)) ->method('setId') - ->willReturnCallback(function ($args) { - static $series = [ - 'MDVA-2470', 'MDVA-2470', 'MDVA-2033' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo('MDVA-2470'), + $this->equalTo('MDVA-2470'), + $this->equalTo('MDVA-2033') + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setTitle') - ->willReturnCallback(function ($args) { - static $series = [ - 'Fix asset locker race condition when using Redis', - 'Fix asset locker race condition when using Redis', - 'Allow DB dumps done with the support module to complete' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo('Fix asset locker race condition when using Redis'), + $this->equalTo('Fix asset locker race condition when using Redis'), + $this->equalTo('Allow DB dumps done with the support module to complete') + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setFilename') - ->willReturnCallback(function ($args) { - static $series = [ - 'MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', - 'MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', - 'MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo('MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'), + $this->equalTo('MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'), + $this->equalTo('MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch') + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setPath') - ->willReturnCallback(function ($args) { - static $series = [ - self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', - self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', - self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo(self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'), + $this->equalTo(self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'), + $this->equalTo(self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch') + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setType') - ->willReturnCallback(function ($args) { - static $series = [ - PatchInterface::TYPE_OPTIONAL, - PatchInterface::TYPE_OPTIONAL, - PatchInterface::TYPE_OPTIONAL - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo(PatchInterface::TYPE_OPTIONAL), + $this->equalTo(PatchInterface::TYPE_OPTIONAL), + $this->equalTo(PatchInterface::TYPE_OPTIONAL) + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageName') - ->willReturnCallback(function ($args) { - static $series = [ - 'magento/magento2-base', - 'magento/magento2-ee-base', - 'magento/magento2-ee-base' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo('magento/magento2-base'), + $this->equalTo('magento/magento2-ee-base'), + $this->equalTo('magento/magento2-ee-base') + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageConstraint') - ->willReturnCallback(function ($args) { - static $series = [ - '2.2.0 - 2.2.5', - '2.2.0 - 2.2.5', - '2.2.0 - 2.2.5' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo('2.2.0 - 2.2.5'), + $this->equalTo('2.2.0 - 2.2.5'), + $this->equalTo('2.2.0 - 2.2.5') + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setRequire') - ->willReturnCallback(function ($args) { - static $series = [ - [], - [], - ['MC-11111', 'MC-22222'] - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo([]), + $this->equalTo([]), + $this->equalTo(['MC-11111', 'MC-22222']) + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setReplacedWith') - ->willReturnCallback(function ($args) { - static $series = [ - '', - '', - 'MC-33333' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo(''), + $this->equalTo(''), + $this->equalTo('MC-33333') + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('setDeprecated') - ->willReturnCallback(function ($args) { - static $series = [ - false, - false, - true - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->with( + $this->logicalOr( + $this->equalTo(false), + $this->equalTo(false), + $this->equalTo(true) + ) + ); + $this->patchBuilder->expects($this->exactly(3)) ->method('build') ->willReturn($this->createMock(Patch::class)); @@ -215,6 +201,7 @@ public function testCollectSuccessful() $this->assertTrue(is_array($this->collector->collect())); } + /** * Tests collecting patches - invalid configuration */ diff --git a/src/Test/Unit/Patch/Conflict/ProcessorTest.php b/src/Test/Unit/Patch/Conflict/ProcessorTest.php index 2f9fc5b..45172a1 100644 --- a/src/Test/Unit/Patch/Conflict/ProcessorTest.php +++ b/src/Test/Unit/Patch/Conflict/ProcessorTest.php @@ -83,7 +83,7 @@ public function testProcess() $this->rollbackProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function() use ($appliedPatch1, $appliedPatch2) { + ->willReturnCallback(function($patch) use ($appliedPatch1, $appliedPatch2) { static $callCount = 0; $expectedPatches = [$appliedPatch1, $appliedPatch2]; if ($patch === $expectedPatches[$callCount]) { @@ -96,12 +96,7 @@ public function testProcess() ->willReturn($rollbackMessages); $this->conflictAnalyzer->expects($this->once()) ->method('analyze') - ->willReturnCallback(function($filter) use ($failedPatch, $patch1) { - if ($filter === $failedPatch) { - return [$patch1]; - } - return []; - }) + ->with($failedPatch) ->willReturn($conflictDetails); $outputMock->expects($this->exactly(2)) ->method('writeln') diff --git a/tests/unit/tmp/junit.xml b/tests/unit/tmp/junit.xml deleted file mode 100644 index 4761d47..0000000 --- a/tests/unit/tmp/junit.xml +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Magento\CloudPatches\Test\Unit\Shell\Command\PatchDriverTest::testApplyFailure -Symfony\Component\Process\Exception\ProcessTimedOutException: The process "'patch' '--silent' '-p1' '--dry-run'" exceeded the timeout of 120 seconds. - -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:1157 -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:424 -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:252 -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:269 -/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:70 -/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:38 -/Users/panney.yadav/Sites/MCLOUD-13149/src/Test/Unit/Shell/Command/PatchDriverTest.php:95 - - - - Magento\CloudPatches\Test\Unit\Shell\Command\PatchDriverTest::testRevertFailure -Symfony\Component\Process\Exception\ProcessTimedOutException: The process "'patch' '--silent' '-p1' '--reverse' '--dry-run'" exceeded the timeout of 60 seconds. - -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:1157 -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:424 -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:252 -/Users/panney.yadav/Sites/MCLOUD-13149/vendor/symfony/process/Process.php:269 -/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:83 -/Users/panney.yadav/Sites/MCLOUD-13149/src/Shell/Command/PatchDriver.php:52 -/Users/panney.yadav/Sites/MCLOUD-13149/src/Test/Unit/Shell/Command/PatchDriverTest.php:131 - - - - - From de054ec3736a306a9570a8c519ca91515c7f45d4 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 28 Jan 2025 12:25:12 +0530 Subject: [PATCH 55/82] Add support of php 8.4 to cloud-patches --- .../Patch/Collector/QualityCollectorTest.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php index f3a1173..90d8e04 100644 --- a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php @@ -134,6 +134,19 @@ public function testCollectSuccessful() ) ); + $this->PatchBuildertest(); + + $this->patchBuilder->expects($this->exactly(3)) + ->method('build') + ->willReturn($this->createMock(Patch::class)); + + $this->assertTrue(is_array($this->collector->collect())); + } + + /** + * patchBuilder function + */ + public function PatchBuildertest(){ $this->patchBuilder->expects($this->exactly(3)) ->method('setType') ->with( @@ -193,12 +206,6 @@ public function testCollectSuccessful() $this->equalTo(true) ) ); - - $this->patchBuilder->expects($this->exactly(3)) - ->method('build') - ->willReturn($this->createMock(Patch::class)); - - $this->assertTrue(is_array($this->collector->collect())); } From b683bd4f0df87bed9d6843831bd5d3f1bd4599f6 Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Tue, 28 Jan 2025 14:47:52 +0530 Subject: [PATCH 56/82] MCLOUD-13240 : Patch for CVE-2025-24434 - Improve-web-api-async --- patches.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches.json b/patches.json index 62a939c..b59583d 100644 --- a/patches.json +++ b/patches.json @@ -297,7 +297,7 @@ ">=2.4.4 <2.4.4-p12": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch", ">=2.4.5 <2.4.5-p11": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch", ">=2.4.6 <2.4.6-p9": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch", - ">=2.4.7 <2.4.7-p5": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch" + ">=2.4.7 <2.4.7-p4": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch" } }, "magento/module-paypal": { From 2781db2d2d142f827db3fcd7d073abad56e737b2 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 28 Jan 2025 18:24:06 +0530 Subject: [PATCH 57/82] Add support of php 8.4 to cloud-patches --- src/Patch/Data/Patch.php | 2 +- .../Action/ApplyOptionalActionTest.php | 21 ++++---- .../Action/ConfirmRequiredActionTest.php | 8 ++-- .../Action/ProcessDeprecatedActionTest.php | 38 +++++++-------- .../Process/Action/RevertActionTest.php | 28 +++++------ .../Unit/Command/Process/ApplyLocalTest.php | 4 +- .../Command/Process/ApplyOptionalTest.php | 2 +- .../Command/Process/ApplyRequiredTest.php | 8 ++-- .../Command/Process/Ece/ApplyOptionalTest.php | 2 +- .../Unit/Command/Process/Ece/RevertTest.php | 8 ++-- .../Unit/Command/Process/RendererTest.php | 2 +- src/Test/Unit/Command/Process/RevertTest.php | 6 +-- .../Unit/Command/Process/ShowStatusTest.php | 8 ++-- src/Test/Unit/Patch/AggregatorTest.php | 48 +++++++++---------- .../Patch/Collector/QualityCollectorTest.php | 5 +- .../Unit/Patch/Conflict/ApplyCheckerTest.php | 6 +-- .../Unit/Patch/Conflict/ProcessorTest.php | 4 +- src/Test/Unit/Patch/Pool/OptionalPoolTest.php | 3 +- 18 files changed, 102 insertions(+), 101 deletions(-) diff --git a/src/Patch/Data/Patch.php b/src/Patch/Data/Patch.php index 4995a17..11622d5 100644 --- a/src/Patch/Data/Patch.php +++ b/src/Patch/Data/Patch.php @@ -152,7 +152,7 @@ public function getId(): string * Set the ID * * @return $this - */ + */ public function setId($id): string { $this->id = $id; diff --git a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php index 4b509f6..61dae14 100644 --- a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php @@ -108,12 +108,12 @@ public function testExecuteSuccessful() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } return []; - }) + }) ->willReturn([$patch1, $patch2, $patch3]); $this->applier->method('apply') @@ -124,7 +124,7 @@ public function testExecuteSuccessful() ]); $this->renderer->expects($this->exactly(3)) ->method('printPatchInfo') - ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ @@ -165,7 +165,7 @@ public function testApplyAlreadyAppliedPatch() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } @@ -180,7 +180,6 @@ public function testApplyAlreadyAppliedPatch() $outputMock->expects($this->once()) ->method('writeln') ->with( - $this->stringContains( 'Patch ' . $patch1->getId() .' (' . $patch1->getFilename() . ') was already applied' ) @@ -224,7 +223,9 @@ public function testApplyingAllPatchesAndSkipDeprecated() $this->renderer->expects($this->once()) ->method('printPatchInfo') ->with( - $outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied' + $outputMock, + $patch1, + 'Patch ' . $patch1->getId() .' has been applied' ); $this->action->execute($inputMock, $outputMock, $patchFilter); @@ -261,7 +262,7 @@ public function testApplyWithException() }); $this->conflictProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ diff --git a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php index 38ae179..aecea22 100644 --- a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php @@ -91,7 +91,7 @@ public function testAskConfirmationForNotAppliedPatches() $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } @@ -108,8 +108,8 @@ public function testAskConfirmationForNotAppliedPatches() $this->renderer->expects($this->once()) ->method('printTable') ->with($outputMock, [$aggregatedPatch]) - ->willReturnCallback(function($output) use ($outputMock, $aggregatedPatch) { - if ($output === $outputMock && $aggregatedPatch === [$aggregatedPatch] ) { + ->willReturnCallback(function ($output) use ($outputMock, $aggregatedPatch) { + if ($output === $outputMock && $aggregatedPatch === [$aggregatedPatch]) { throw new RuntimeException('Error message'); } return null; @@ -159,7 +159,7 @@ public function testConfirmationRejected() $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } diff --git a/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php b/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php index 717335f..70b6cdf 100644 --- a/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php @@ -105,16 +105,16 @@ public function testProcessDeprecationSuccessful() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } return []; - }) + }) ->willReturn([$patchMock]); $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->willReturnCallback(function($patchId) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($patchId) use ($patchFilter, $patch1) { if ($patchId === $patch1->getId()) { return [$patch1]; } @@ -128,7 +128,7 @@ public function testProcessDeprecationSuccessful() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function($patchId) use ($patchFilter) { + ->willReturnCallback(function ($patchId) use ($patchFilter) { if ($patchId === $expectedMessage) { $this->stringContains($expectedMessage); } @@ -157,7 +157,7 @@ public function testProcessDeprecationException() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } @@ -204,12 +204,12 @@ public function testProcessReplacementSuccessful() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } return []; - }) + }) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -218,7 +218,7 @@ public function testProcessReplacementSuccessful() $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } @@ -228,7 +228,7 @@ public function testProcessReplacementSuccessful() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function($patchId) use ($patchFilter) { + ->willReturnCallback(function ($patchId) use ($patchFilter) { if ($patchId === $expectedMessage) { $this->stringContains($expectedMessage); } @@ -263,12 +263,12 @@ public function testSkippingReplacementProcessForAppliedPatch() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } return []; - }) + }) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -303,7 +303,7 @@ public function testProcessReplacementException() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } @@ -317,7 +317,7 @@ public function testProcessReplacementException() $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->willReturnCallback(function($patchId) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($patchId) use ($patchFilter, $patch1) { if ($patchId === $patch1->getId()) { return [$patch1]; } diff --git a/src/Test/Unit/Command/Process/Action/RevertActionTest.php b/src/Test/Unit/Command/Process/Action/RevertActionTest.php index ac13a40..469f0d7 100644 --- a/src/Test/Unit/Command/Process/Action/RevertActionTest.php +++ b/src/Test/Unit/Command/Process/Action/RevertActionTest.php @@ -58,8 +58,8 @@ class RevertActionTest extends TestCase private $revertValidator; /** - * @var revertAction|MockObject - */ + * @var revertAction|MockObject + */ protected $revertAction; /** @@ -110,12 +110,12 @@ public function testExecuteSuccessful() $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { + if ($filter === $patchFilter) { + return [$patch1]; + } return []; - }) + }) ->willReturn([$patch1, $patch2]); $this->applier->method('revert') @@ -126,7 +126,7 @@ public function testExecuteSuccessful() $this->renderer->expects($this->exactly(2)) ->method('printPatchInfo') - ->willReturnCallback(function($patch, $message) use ($patch1, $patch2) { + ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2) { static $callCount = 0; $expectedPatches = [$patch1, $patch2]; $expectedMessages = [ @@ -140,7 +140,7 @@ public function testExecuteSuccessful() } return false; - }); + }); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -165,7 +165,7 @@ public function testRevertNotAppliedPatch() $outputMock = $this->createMock(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchFilter, $patch1) { + ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { if ($filter === $patchFilter) { return [$patch1]; } @@ -180,7 +180,7 @@ public function testRevertNotAppliedPatch() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function($patchId) use ($patchFilter,$patch1) { + ->willReturnCallback(function ($patchId) use ($patchFilter, $patch1) { if ($patchId === $patch1->getId()) { $this->stringContains( 'Patch ' . $patch1->getId() . ' (' . $patch1->getFilename() . ') is not applied' @@ -214,7 +214,7 @@ public function testRevertWithException() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function($patchId) use ($patchFilter) { + ->willReturnCallback(function ($patchId) use ($patchFilter) { if ($patchId === $errorMessage) { $this->stringContains($errorMessage); } @@ -235,7 +235,7 @@ public function testPatchNotFoundException() $inputMock = $this->createMock(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ $outputMock = $this->createMock(OutputInterface::class); - $this->optionalPool->expects($this->once()) + $this->optionalPool->expects($this->once()) ->method('getList') ->with($patchFilter) ->willThrowException(new PatchNotFoundException('')); @@ -256,7 +256,7 @@ public function testValidationFailedException() /** @var OutputInterface|MockObject $outputMock */ $outputMock = $this->createMock(OutputInterface::class); - $this->revertValidator->expects($this->once()) + $this->revertValidator->expects($this->once()) ->method('validate') ->with($patchFilter) ->willThrowException(new RuntimeException('Error')); diff --git a/src/Test/Unit/Command/Process/ApplyLocalTest.php b/src/Test/Unit/Command/Process/ApplyLocalTest.php index f97417c..742e109 100644 --- a/src/Test/Unit/Command/Process/ApplyLocalTest.php +++ b/src/Test/Unit/Command/Process/ApplyLocalTest.php @@ -125,7 +125,7 @@ public function testApplySuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ @@ -180,7 +180,7 @@ function ($path, $title) { $this->rollbackProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function($filter) use ($patch1) { + ->willReturnCallback(function ($filter) use ($patch1) { if ($filter === $patch1) { return [$patch1]; } diff --git a/src/Test/Unit/Command/Process/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/ApplyOptionalTest.php index ef08794..0825517 100644 --- a/src/Test/Unit/Command/Process/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/ApplyOptionalTest.php @@ -80,7 +80,7 @@ public function testApplyWithPatchArgumentProvided() ->with($cliPatchArgument) ->willReturn($cliPatchArgument); - $this->actionPool->expects($this->once()) + $this->actionPool->expects($this->once()) ->method('execute') ->with($inputMock, $outputMock, $cliPatchArgument); $this->applyOptional->run($inputMock, $outputMock); diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index c4478d8..c5ab3d2 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -103,7 +103,7 @@ public function testApplySuccessful() $this->renderer->expects($this->exactly(3)) ->method('printPatchInfo') - ->willReturnCallback(function($patch, $message) use ($patch1, $patch2, $patch3) { + ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ @@ -140,15 +140,15 @@ public function testApplyWithException() $this->applier->method('apply') ->with( - $this->logicalOr($this->equalTo($patch->getPath()), $this->equalTo($patch->getId())) + $this->logicalOr($this->equalTo($patch->getPath()), $this->equalTo($patch->getId())) ) ->willThrowException(new ApplierException('Applier error message')); $this->conflictProcessor->expects($this->once()) ->method('process') ->with( - $this->logicalOr($this->equalTo($outputMock), $this->equalTo($patch), $this->equalTo([]), $this->equalTo('Applier error message')) - ) + $this->logicalOr($this->equalTo($outputMock), $this->equalTo($patch), $this->equalTo([]), $this->equalTo('Applier error message')) + ) ->willThrowException(new RuntimeException('Error message')); $this->expectException(RuntimeException::class); diff --git a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php index 9a4b881..52bb6dc 100644 --- a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php @@ -89,7 +89,7 @@ public function testApplyWithPatchEnvVariableProvided() $this->actionPool->expects($this->once()) ->method('execute') ->with($inputMock, $outputMock, $configQualityPatches) - ->willReturnCallback(function($input, $output, $config) use ($inputMock, $outputMock, $configQualityPatches) { + ->willReturnCallback(function ($input, $output, $config) use ($inputMock, $outputMock, $configQualityPatches) { if ($input === $inputMock && $output === $outputMock && $config === $configQualityPatches) { return true; } diff --git a/src/Test/Unit/Command/Process/Ece/RevertTest.php b/src/Test/Unit/Command/Process/Ece/RevertTest.php index 69c178c..6663b31 100644 --- a/src/Test/Unit/Command/Process/Ece/RevertTest.php +++ b/src/Test/Unit/Command/Process/Ece/RevertTest.php @@ -116,7 +116,7 @@ public function testRevertSuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function($patch, $message) use ($patch1, $patch2) { + ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2) { static $callCount = 0; $expectedPatches = [$patch1, $patch2, $patch3]; $expectedMessages = [ @@ -135,8 +135,8 @@ public function testRevertSuccessful() $this->revertAction->expects($this->once()) ->method('execute') ->with($inputMock, $outputMock, []) - ->willReturnCallback(function($input, $output) use ($inputMock, $outputMock) { - if ($output === $outputMock && $input === $inputMock && $patch === [] ) { + ->willReturnCallback(function ($input, $output) use ($inputMock, $outputMock) { + if ($output === $outputMock && $input === $inputMock && $patch === []) { return true; } return false; @@ -183,7 +183,7 @@ function ($path, $title) { $this->revertAction->expects($this->once()) ->method('execute') ->with($inputMock, $outputMock) - ->willReturnCallback(function($input, $output) use ($inputMock, $outputMock) { + ->willReturnCallback(function ($input, $output) use ($inputMock, $outputMock) { if ($output === $outputMock && $input === $inputMock) { return true; } diff --git a/src/Test/Unit/Command/Process/RendererTest.php b/src/Test/Unit/Command/Process/RendererTest.php index 992e997..6e65b5e 100644 --- a/src/Test/Unit/Command/Process/RendererTest.php +++ b/src/Test/Unit/Command/Process/RendererTest.php @@ -76,7 +76,7 @@ public function testPrintPatchInfo(PatchInterface $patch, string $prependedMessa $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $outputMock->expects($this->atLeastOnce()) ->method('writeln') - ->willReturnCallback(function($filter) use ($expectedArray) { + ->willReturnCallback(function ($filter) use ($expectedArray) { if ($filter === $expectedArray) { return $expectedArray; } diff --git a/src/Test/Unit/Command/Process/RevertTest.php b/src/Test/Unit/Command/Process/RevertTest.php index 793c397..ac651b7 100644 --- a/src/Test/Unit/Command/Process/RevertTest.php +++ b/src/Test/Unit/Command/Process/RevertTest.php @@ -84,7 +84,7 @@ public function testRevertWithPatchArgumentProvided() ->willReturn($cliOptAll); $this->filterFactory->method('createRevertFilter') ->with($cliOptAll, $cliPatchArgument) - ->willReturnCallback(function($patches) use ($cliOptAll, $cliPatchArgument) { + ->willReturnCallback(function ($patches) use ($cliOptAll, $cliPatchArgument) { if ($patches === $cliOptAll && $patches === $cliPatchArgument) { return true; } @@ -95,7 +95,7 @@ public function testRevertWithPatchArgumentProvided() $this->revertAction->expects($this->once()) ->method('execute') ->with($inputMock, $outputMock, $cliPatchArgument) - ->willReturnCallback(function($input, $output, $cliPatch) use ($inputMock, $outputMock, $cliPatchArgument) { + ->willReturnCallback(function ($input, $output, $cliPatch) use ($inputMock, $outputMock, $cliPatchArgument) { if ($input === $inputMock && $output === $outputMock && $cliPatch === $cliPatchArgument) { return true; } @@ -129,7 +129,7 @@ public function testRevertWithEmptyPatchArgument() ->willReturn($cliOptAll); $this->filterFactory->method('createRevertFilter') ->with($cliOptAll, $cliPatchArgument) - ->willReturnCallback(function($cliOpt, $cliPatch) use ($cliOptAll, $cliPatchArgument) { + ->willReturnCallback(function ($cliOpt, $cliPatch) use ($cliOptAll, $cliPatchArgument) { if ($cliOpt === $cliOptAll && $cliPatch === $cliPatchArgument) { return true; } diff --git a/src/Test/Unit/Command/Process/ShowStatusTest.php b/src/Test/Unit/Command/Process/ShowStatusTest.php index e6eee5e..56f2884 100644 --- a/src/Test/Unit/Command/Process/ShowStatusTest.php +++ b/src/Test/Unit/Command/Process/ShowStatusTest.php @@ -140,8 +140,8 @@ public function testShowStatus() $this->reviewAppliedAction->expects($this->once()) ->method('execute') - ->willReturnCallback(function($input, $output, $patches) use ($inputMock, $outputMock) { - if ($input === $outputMock && $output === $outputMock ) { + ->willReturnCallback(function ($input, $output, $patches) use ($inputMock, $outputMock) { + if ($input === $outputMock && $output === $outputMock) { return true; } return false; @@ -158,7 +158,7 @@ public function testShowStatus() // Show warning message about patch deprecation $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function($filter) use ($patch1) { + ->willReturnCallback(function ($filter) use ($patch1) { if ($filter === $patch1->getId()) { $this->anything(); $this->stringContains('Deprecated patch ' . $patch1->getId() . ' is currently applied'); @@ -170,7 +170,7 @@ public function testShowStatus() $this->renderer->expects($this->once()) ->method('printTable') ->with($outputMock, [$patch1, $patch2, $patch5]) - ->willReturnCallback(function($output, $patches) use ($outputMock, $patch, $patch2, $patch5) { + ->willReturnCallback(function ($output, $patches) use ($outputMock, $patch, $patch2, $patch5) { if ($output === $outputMock && $patches === [$patch2] && $patches === [$patch2] && $patches === [$patch2]) { return true; } diff --git a/src/Test/Unit/Patch/AggregatorTest.php b/src/Test/Unit/Patch/AggregatorTest.php index 8ef3f69..d482bb3 100644 --- a/src/Test/Unit/Patch/AggregatorTest.php +++ b/src/Test/Unit/Patch/AggregatorTest.php @@ -39,29 +39,29 @@ protected function setUp(): void } /** - * Tests patch aggregation. - */ -public function testAggregate() -{ - $patch1CE = $this->createPatch('MC-1', 'Patch1 CE'); - $patch1EE = $this->createPatch('MC-1', 'Patch1 EE'); - $patch1B2B = $this->createPatch('MC-1', 'Patch1 B2B'); - $patch2CE = $this->createPatch('MC-2', 'Patch2 CE'); - $patch2EE = $this->createPatch('MC-2', 'Patch2 EE'); - $patch3 = $this->createPatch('MC-3', 'Patch3'); + * Tests patch aggregation. + */ + public function testAggregate() + { + $patch1CE = $this->createPatch('MC-1', 'Patch1 CE'); + $patch1EE = $this->createPatch('MC-1', 'Patch1 EE'); + $patch1B2B = $this->createPatch('MC-1', 'Patch1 B2B'); + $patch2CE = $this->createPatch('MC-2', 'Patch2 CE'); + $patch2EE = $this->createPatch('MC-2', 'Patch2 EE'); + $patch3 = $this->createPatch('MC-3', 'Patch3'); - // Mock AggregatedPatchInterface to return the patches when getPatches is called - $aggregatedPatchMock1 = $this->createMock(AggregatedPatchInterface::class); - $aggregatedPatchMock1->method('getRequire')->willReturn([$patch1CE, $patch1EE, $patch1B2B]); + // Mock AggregatedPatchInterface to return the patches when getPatches is called + $aggregatedPatchMock1 = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatchMock1->method('getRequire')->willReturn([$patch1CE, $patch1EE, $patch1B2B]); - $aggregatedPatchMock2 = $this->createMock(AggregatedPatchInterface::class); - $aggregatedPatchMock2->method('getRequire')->willReturn([$patch2CE, $patch2EE]); + $aggregatedPatchMock2 = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatchMock2->method('getRequire')->willReturn([$patch2CE, $patch2EE]); - $aggregatedPatchMock3 = $this->createMock(AggregatedPatchInterface::class); - $aggregatedPatchMock3->method('getRequire')->willReturn([$patch3]); + $aggregatedPatchMock3 = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatchMock3->method('getRequire')->willReturn([$patch3]); - // Setting up the factory mock to return AggregatedPatchInterface mocks - $this->aggregatedPatchFactory->expects($this->exactly(3)) + // Setting up the factory mock to return AggregatedPatchInterface mocks + $this->aggregatedPatchFactory->expects($this->exactly(3)) ->method('create') ->willReturnOnConsecutiveCalls( $aggregatedPatchMock1, // First call returns this AggregatedPatchInterface mock @@ -69,12 +69,12 @@ public function testAggregate() $aggregatedPatchMock3 // Third call returns this AggregatedPatchInterface mock ); - $result = $this->aggregator->aggregate( - [$patch1CE, $patch1EE, $patch1B2B, $patch2CE, $patch2EE, $patch3] - ); + $result = $this->aggregator->aggregate( + [$patch1CE, $patch1EE, $patch1B2B, $patch2CE, $patch2EE, $patch3] + ); - $this->assertTrue(is_array($result)); -} + $this->assertTrue(is_array($result)); + } /** diff --git a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php index 90d8e04..24a4086 100644 --- a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php @@ -144,9 +144,10 @@ public function testCollectSuccessful() } /** - * patchBuilder function + * patchBuilder function */ - public function PatchBuildertest(){ + public function patchBuilderTest() + { $this->patchBuilder->expects($this->exactly(3)) ->method('setType') ->with( diff --git a/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php b/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php index 5f06c8e..37ffaeb 100644 --- a/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php +++ b/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php @@ -68,12 +68,12 @@ public function testCheck() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function($filter) use ($patchIds, $patch1) { + ->willReturnCallback(function ($filter) use ($patchIds, $patch1) { if ($filter === $patchIds) { return [$patch1]; - } + } return []; - }) + }) ->willReturn([$patch1, $patch2, $patch3]); $this->filesystem->expects($this->exactly(3)) ->method('get') diff --git a/src/Test/Unit/Patch/Conflict/ProcessorTest.php b/src/Test/Unit/Patch/Conflict/ProcessorTest.php index 45172a1..8656c85 100644 --- a/src/Test/Unit/Patch/Conflict/ProcessorTest.php +++ b/src/Test/Unit/Patch/Conflict/ProcessorTest.php @@ -83,7 +83,7 @@ public function testProcess() $this->rollbackProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function($patch) use ($appliedPatch1, $appliedPatch2) { + ->willReturnCallback(function ($patch) use ($appliedPatch1, $appliedPatch2) { static $callCount = 0; $expectedPatches = [$appliedPatch1, $appliedPatch2]; if ($patch === $expectedPatches[$callCount]) { @@ -100,7 +100,7 @@ public function testProcess() ->willReturn($conflictDetails); $outputMock->expects($this->exactly(2)) ->method('writeln') - ->willReturnCallback(function($filter) use ($failedPatch) { + ->willReturnCallback(function ($filter) use ($failedPatch) { if ($filter === $failedPatch->getId() && $filter === $rollbackMessages) { $this->stringContains('Error: patch ' . $failedPatch->getId() . ' can\'t be applied'); $rollbackMessages; diff --git a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php index 0c2e172..b6cc39c 100644 --- a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php +++ b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php @@ -327,10 +327,9 @@ private function createPatch(string $id, array $require = [], string $replacedWi $patch->method('getOrigin')->willReturn(SupportCollector::ORIGIN); // To avoid dynamically adding properties, use __toString method instead - $patch->method('__toString')->willReturn($id); + $patch->method('__toString')->willReturn($id); return $patch; - } /** From a4506116286cee849242295a6e353059b5bc6da4 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 28 Jan 2025 20:04:07 +0530 Subject: [PATCH 58/82] Add support of php 8.4 to cloud-patches --- src/App/GenericException.php | 2 +- .../Process/Action/ApplyOptionalActionTest.php | 5 +---- .../Unit/Command/Process/ApplyRequiredTest.php | 10 +++++----- src/Test/Unit/Patch/AggregatorTest.php | 1 - .../Unit/Patch/Collector/QualityCollectorTest.php | 15 ++++++++++----- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/App/GenericException.php b/src/App/GenericException.php index f7b1eea..60fbd9f 100644 --- a/src/App/GenericException.php +++ b/src/App/GenericException.php @@ -19,7 +19,7 @@ class GenericException extends \Exception * @param int $code * @param Throwable|null $previous */ - public function __construct(string $message ="", int $code = 0, ?Throwable $previous = null) + public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php index 61dae14..6c8ae3f 100644 --- a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php @@ -142,9 +142,7 @@ public function testExecuteSuccessful() }); $this->action->execute($inputMock, $outputMock, $patchFilter); } - - - + /** * Tests successful optional patches applying. * @@ -188,7 +186,6 @@ public function testApplyAlreadyAppliedPatch() $this->action->execute($inputMock, $outputMock, $patchFilter); } - /** * Tests successful optional patches applying. * diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index c5ab3d2..42bf6f2 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -145,12 +145,12 @@ public function testApplyWithException() ->willThrowException(new ApplierException('Applier error message')); $this->conflictProcessor->expects($this->once()) - ->method('process') - ->with( - $this->logicalOr($this->equalTo($outputMock), $this->equalTo($patch), $this->equalTo([]), $this->equalTo('Applier error message')) - ) + ->method('process') + ->with( + $this->logicalOr($this->equalTo($outputMock), $this->equalTo($patch), $this->equalTo([]), $this->equalTo('Applier error message')) + ) ->willThrowException(new RuntimeException('Error message')); - + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); diff --git a/src/Test/Unit/Patch/AggregatorTest.php b/src/Test/Unit/Patch/AggregatorTest.php index d482bb3..3ab331a 100644 --- a/src/Test/Unit/Patch/AggregatorTest.php +++ b/src/Test/Unit/Patch/AggregatorTest.php @@ -76,7 +76,6 @@ public function testAggregate() $this->assertTrue(is_array($result)); } - /** * Creates patch mock. * diff --git a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php index 24a4086..4493023 100644 --- a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php @@ -128,9 +128,15 @@ public function testCollectSuccessful() ->method('setPath') ->with( $this->logicalOr( - $this->equalTo(self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'), - $this->equalTo(self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'), - $this->equalTo(self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch') + $this->equalTo( + self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch' + ), + $this->equalTo( + self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch' + ), + $this->equalTo( + self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch' + ) ) ); @@ -209,7 +215,6 @@ public function patchBuilderTest() ); } - /** * Tests collecting patches - invalid configuration */ @@ -218,7 +223,7 @@ public function testInvalidConfiguration() $config = require __DIR__ . '/Fixture/quality_config_invalid.php'; $expectedExceptionMessage = 'Patch MDVA-2033 has invalid configuration:' . - PHP_EOL . ' - Property \'file\' is not found in \'2.2.0 - 2.2.5\'' . + PHP_EOL . ' - Property \'file\' is not found in \'2.2.0 - 2.2.5\' ' . PHP_EOL . ' - Property \'require\' from \'2.2.0 - 2.2.5\' should have an array type' . PHP_EOL . ' - Property \'replaced-with\' from \'2.2.0 - 2.2.5\' should have a string type' . PHP_EOL . ' - Property \'deprecated\' from \'2.2.0 - 2.2.5\' should have a boolean type'; From e25eb391d646ea86756322f881b730ae72b0024f Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 28 Jan 2025 20:27:21 +0530 Subject: [PATCH 59/82] Add support of php 8.4 to cloud-patches --- src/Test/Unit/Command/Process/ApplyRequiredTest.php | 9 +++++++-- src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php | 3 ++- src/Test/Unit/Command/Process/RevertTest.php | 3 ++- src/Test/Unit/Command/Process/ShowStatusTest.php | 6 ++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index 42bf6f2..de335c7 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -147,10 +147,15 @@ public function testApplyWithException() $this->conflictProcessor->expects($this->once()) ->method('process') ->with( - $this->logicalOr($this->equalTo($outputMock), $this->equalTo($patch), $this->equalTo([]), $this->equalTo('Applier error message')) + $this->logicalOr( + $this->equalTo($outputMock), + $this->equalTo($patch), + $this->equalTo([]), + $this->equalTo('Applier error message') + ) ) ->willThrowException(new RuntimeException('Error message')); - + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); diff --git a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php index 52bb6dc..560081a 100644 --- a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php @@ -89,7 +89,8 @@ public function testApplyWithPatchEnvVariableProvided() $this->actionPool->expects($this->once()) ->method('execute') ->with($inputMock, $outputMock, $configQualityPatches) - ->willReturnCallback(function ($input, $output, $config) use ($inputMock, $outputMock, $configQualityPatches) { + ->willReturnCallback(function ($input, $output, $config) + use ($inputMock, $outputMock, $configQualityPatches) { if ($input === $inputMock && $output === $outputMock && $config === $configQualityPatches) { return true; } diff --git a/src/Test/Unit/Command/Process/RevertTest.php b/src/Test/Unit/Command/Process/RevertTest.php index ac651b7..4cef6a4 100644 --- a/src/Test/Unit/Command/Process/RevertTest.php +++ b/src/Test/Unit/Command/Process/RevertTest.php @@ -95,7 +95,8 @@ public function testRevertWithPatchArgumentProvided() $this->revertAction->expects($this->once()) ->method('execute') ->with($inputMock, $outputMock, $cliPatchArgument) - ->willReturnCallback(function ($input, $output, $cliPatch) use ($inputMock, $outputMock, $cliPatchArgument) { + ->willReturnCallback(function ($input, $output, $cliPatch) + use ($inputMock, $outputMock, $cliPatchArgument) { if ($input === $inputMock && $output === $outputMock && $cliPatch === $cliPatchArgument) { return true; } diff --git a/src/Test/Unit/Command/Process/ShowStatusTest.php b/src/Test/Unit/Command/Process/ShowStatusTest.php index 56f2884..8d3eca2 100644 --- a/src/Test/Unit/Command/Process/ShowStatusTest.php +++ b/src/Test/Unit/Command/Process/ShowStatusTest.php @@ -170,8 +170,10 @@ public function testShowStatus() $this->renderer->expects($this->once()) ->method('printTable') ->with($outputMock, [$patch1, $patch2, $patch5]) - ->willReturnCallback(function ($output, $patches) use ($outputMock, $patch, $patch2, $patch5) { - if ($output === $outputMock && $patches === [$patch2] && $patches === [$patch2] && $patches === [$patch2]) { + ->willReturnCallback(function ($output, $patches) + use ($outputMock, $patch, $patch2, $patch5) { + if ($output === $outputMock && $patches === [$patch2] + && $patches === [$patch2] && $patches === [$patch2]) { return true; } return false; From 24a0d768e34ecee279054e7ef2e8a9912e95ee52 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Tue, 28 Jan 2025 21:13:20 +0530 Subject: [PATCH 60/82] Add support of php 8.4 to cloud-patches --- src/Test/Unit/Patch/Collector/QualityCollectorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php index 4493023..ec71c38 100644 --- a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php @@ -223,7 +223,7 @@ public function testInvalidConfiguration() $config = require __DIR__ . '/Fixture/quality_config_invalid.php'; $expectedExceptionMessage = 'Patch MDVA-2033 has invalid configuration:' . - PHP_EOL . ' - Property \'file\' is not found in \'2.2.0 - 2.2.5\' ' . + PHP_EOL . ' - Property \'file\' is not found in \'2.2.0 - 2.2.5\'' . PHP_EOL . ' - Property \'require\' from \'2.2.0 - 2.2.5\' should have an array type' . PHP_EOL . ' - Property \'replaced-with\' from \'2.2.0 - 2.2.5\' should have a string type' . PHP_EOL . ' - Property \'deprecated\' from \'2.2.0 - 2.2.5\' should have a boolean type'; From 4a5170903cbf43d2e98b5adef18b6a3d768934f1 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Wed, 29 Jan 2025 14:03:03 +0530 Subject: [PATCH 61/82] src/Patch/Data/Patch.php --- src/Patch/Data/Patch.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Patch/Data/Patch.php b/src/Patch/Data/Patch.php index 11622d5..5e1deb4 100644 --- a/src/Patch/Data/Patch.php +++ b/src/Patch/Data/Patch.php @@ -148,17 +148,6 @@ public function getId(): string return $this->id; } - /** - * Set the ID - * - * @return $this - */ - public function setId($id): string - { - $this->id = $id; - return $this; - } - /** * @inheritDoc */ From bbe65155a18ebfa6b4f22a2d35ca0a78f6780c81 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Wed, 29 Jan 2025 14:08:27 +0530 Subject: [PATCH 62/82] Add support of php 8.4 to cloud-patches --- .../Acceptance/Acceptance84Cest.php | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/Test/Functional/Acceptance/Acceptance84Cest.php diff --git a/src/Test/Functional/Acceptance/Acceptance84Cest.php b/src/Test/Functional/Acceptance/Acceptance84Cest.php deleted file mode 100644 index fac3002..0000000 --- a/src/Test/Functional/Acceptance/Acceptance84Cest.php +++ /dev/null @@ -1,24 +0,0 @@ - '2.4.8', 'magentoVersion' => '2.4.8'], - ]; - } -} From 44624e05a7398eb8d64be26fcd530fa3fe5979e5 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Fri, 31 Jan 2025 19:22:28 +0530 Subject: [PATCH 63/82] Add support of php 8.4 to cloud-patches --- composer.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e128d49..50a7edb 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "symfony/process": "^4.4 || ^5.1 || ^5.4 || ^6.4", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", "symfony/yaml": "^4.4 || ^5.1 || ^5.4 || ^6.4", - "magento/quality-patches": "^1.1.0" + "magento/quality-patches": "^1.1.0", + "magento/magento-cloud-docker":"^1.4.0" }, "require-dev": { "codeception/codeception": "^4.1 || ^5.1", @@ -32,7 +33,7 @@ "codeception/module-rest": "^1.2 || ^3.0", "consolidation/robo": "^1.2 || ^3.0 || ^5.0", "phpmd/phpmd": "@stable", - "phpunit/phpunit": "^9.5.10 || ^10", + "phpunit/phpunit": "^10.0", "squizlabs/php_codesniffer": "^3.0" }, "bin": [ From a831afe71bf90c541b0058066709c4b0305ab479 Mon Sep 17 00:00:00 2001 From: glo42671 Date: Mon, 3 Feb 2025 11:36:13 +0530 Subject: [PATCH 64/82] Add support of php 8.4 to cloud-patches --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 50a7edb..f950518 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,7 @@ "symfony/process": "^4.4 || ^5.1 || ^5.4 || ^6.4", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", "symfony/yaml": "^4.4 || ^5.1 || ^5.4 || ^6.4", - "magento/quality-patches": "^1.1.0", - "magento/magento-cloud-docker":"^1.4.0" + "magento/quality-patches": "^1.1.0" }, "require-dev": { "codeception/codeception": "^4.1 || ^5.1", From 268e408da601c637629530d3f6a3e95ce5277dcb Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Thu, 6 Feb 2025 17:50:35 +0530 Subject: [PATCH 65/82] bump version to 1.1.3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f950518..d969fa9 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.2", + "version": "1.1.3", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From fc5143cbf6510114d3736d5ea450cd5ca2097e57 Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Thu, 13 Feb 2025 17:04:44 +0530 Subject: [PATCH 66/82] Bump version to 1.1.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d969fa9..9b67a3c 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.3", + "version": "1.1.4", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From de45a4a73e937f7b0a0b0801e04f62fb263c7682 Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Fri, 28 Mar 2025 12:48:14 +0530 Subject: [PATCH 67/82] MCLOUD-13240 : Patch for CVE-2025-24434 - Improve-web-api-async --- ...5_24434_improve_web_api_async__2.4.4.patch | 793 +----------- ...5_24434_improve_web_api_async__2.4.5.patch | 765 +----------- ...5_24434_improve_web_api_async__2.4.6.patch | 1073 ++++------------- ...5_24434_improve_web_api_async__2.4.7.patch | 138 +-- 4 files changed, 348 insertions(+), 2421 deletions(-) diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch index 0c79765..a592863 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch @@ -1,20 +1,3 @@ - - ignores group_id - ---- - .../Customer/Model/AccountManagement.php | 5 - - .../Customer/Model/AccountManagementApi.php | 132 ++++++ - ...AsyncRequestCustomerGroupAuthorization.php | 78 ++++ - .../Unit/Model/AccountManagementApiTest.php | 421 ++++++++++++++++++ - .../Test/Unit/Model/AccountManagementTest.php | 4 - - ...cRequestCustomerGroupAuthorizationTest.php | 112 +++++ - app/code/Magento/Customer/composer.json | 3 +- - app/code/Magento/Customer/etc/di.xml | 5 + - 8 files changed, 750 insertions(+), 10 deletions(-) - create mode 100644 app/code/Magento/Customer/Plugin/AsyncRequestCustomerGroupAuthorization.php - create mode 100644 app/code/Magento/Customer/Test/Unit/Model/AccountManagementApiTest.php - create mode 100644 app/code/Magento/Customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php - diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php index 6e0aac11d8e98..27c2bf4051ccc 100644 --- a/vendor/magento/module-customer/Model/AccountManagement.php @@ -36,9 +19,9 @@ index 02a05705b57ef..8b4f78ab26c77 100644 --- a/vendor/magento/module-customer/Model/AccountManagementApi.php +++ b/vendor/magento/module-customer/Model/AccountManagementApi.php @@ -6,16 +6,127 @@ - + namespace Magento\Customer\Model; - + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; @@ -63,7 +46,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 +use Magento\Framework\Stdlib\StringUtils as StringHelper; +use Magento\Store\Model\StoreManagerInterface; +use Psr\Log\LoggerInterface as PsrLogger; - + /** * Account Management service implementation for external API access. + * @@ -170,7 +153,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 + $this->validateCustomerRequest($customer); $customer = parent::createAccount($customer, $password, $redirectUrl); $customer->setConfirmation(null); - + return $customer; } + @@ -196,10 +179,10 @@ index 02a05705b57ef..8b4f78ab26c77 100644 } diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php new file mode 100644 -index 0000000000000..5b5c8ce1fc0ca +index 0000000000000..295b33d2db14a --- /dev/null +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -0,0 +1,78 @@ +@@ -0,0 +1,89 @@ +customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); -+ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); -+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); -+ $this->random = $this->createMock(Random::class); -+ $this->validator = $this->createMock(Validator::class); -+ $this->validationResultsInterfaceFactory = $this->createMock( -+ ValidationResultsInterfaceFactory::class -+ ); -+ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); -+ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); -+ $this->customerRegistry = $this->createMock(CustomerRegistry::class); -+ -+ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); -+ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); -+ $this->share = $this->createMock(Share::class); -+ $this->string = $this->createMock(StringUtils::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->transportBuilder = $this->createMock(TransportBuilder::class); -+ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); -+ $this->registry = $this->createMock(Registry::class); -+ $this->customerViewHelper = $this->createMock(View::class); -+ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); -+ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); -+ $this->objectFactory = $this->createMock(DataObjectFactory::class); -+ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); -+ $this->extensibleDataObjectConverter = $this->createMock( -+ ExtensibleDataObjectConverter::class -+ ); -+ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); -+ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) -+ ->onlyMethods(['addData', 'setData']) -+ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); -+ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); -+ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); -+ -+ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) -+ ->disableOriginalConstructor() -+ ->onlyMethods(['create']) -+ ->getMock(); -+ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->authorizationMock = $this->createMock(Authorization::class); -+ $this->objectManagerHelper = new ObjectManagerHelper($this); -+ $this->accountManagement = $this->objectManagerHelper->getObject( -+ AccountManagementApi::class, -+ [ -+ 'customerFactory' => $this->customerFactory, -+ 'eventManager' => $this->manager, -+ 'storeManager' => $this->storeManager, -+ 'mathRandom' => $this->random, -+ 'validator' => $this->validator, -+ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, -+ 'addressRepository' => $this->addressRepository, -+ 'customerMetadataService' => $this->customerMetadata, -+ 'customerRegistry' => $this->customerRegistry, -+ 'logger' => $this->logger, -+ 'encryptor' => $this->encryptor, -+ 'configShare' => $this->share, -+ 'stringHelper' => $this->string, -+ 'customerRepository' => $this->customerRepository, -+ 'scopeConfig' => $this->scopeConfig, -+ 'transportBuilder' => $this->transportBuilder, -+ 'dataProcessor' => $this->dataObjectProcessor, -+ 'registry' => $this->registry, -+ 'customerViewHelper' => $this->customerViewHelper, -+ 'dateTime' => $this->dateTime, -+ 'customerModel' => $this->customer, -+ 'objectFactory' => $this->objectFactory, -+ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, -+ 'dateTimeFactory' => $this->dateTimeFactory, -+ 'accountConfirmation' => $this->accountConfirmation, -+ 'sessionManager' => $this->sessionManager, -+ 'saveHandler' => $this->saveHandler, -+ 'visitorCollectionFactory' => $this->visitorCollectionFactory, -+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, -+ 'addressRegistry' => $this->addressRegistryMock, -+ 'allowedCountriesReader' => $this->allowedCountriesReader, -+ 'authorization' => $this->authorizationMock -+ ] -+ ); -+ $this->accountManagementMock = $this->createMock(AccountManagement::class); -+ -+ $this->storeMock = $this->getMockBuilder( -+ StoreInterface::class -+ )->disableOriginalConstructor() -+ ->getMock(); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @throws LocalizedException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforeCreateAccount( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ -+ $customer = $this->getMockBuilder(CustomerInterface::class) -+ ->addMethods(['setData']) -+ ->getMockForAbstractClass(); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $customer->method('getWebsiteId')->willReturn(2); -+ $customer->method('getStoreId')->willReturn(1); -+ $customer->method('setData')->willReturn(1); -+ -+ $this->customerRepository->method('get')->willReturn($customer); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $this->customerRepository->method('save')->willReturn($customer); -+ -+ if (!$willThrowException) { -+ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); -+ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); -+ $this->random->method('getUniqueHash')->willReturn('testabc'); -+ $date = $this->getMockBuilder(\DateTime::class) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory->expects(static::once()) -+ ->method('create') -+ ->willReturn($date); -+ $date->expects(static::once()) -+ ->method('format') -+ ->with('Y-m-d H:i:s') -+ ->willReturn('2015-01-01 00:00:00'); -+ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); -+ $this->storeManager->method('getStores') -+ ->willReturn([$this->storeMock]); -+ } -+ $this->accountManagement->createAccount($customer); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -index 8ff6a8585212f..cbe0a18e4b178 100644 ---- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - $defaultGroupId = 1; -- $requestedGroupId = 3; - - $datetime = $this->prepareDateTimeFactory(); - -@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void - return null; - } - })); -- $customer->expects($this->atLeastOnce()) -- ->method('getGroupId') -- ->willReturn($requestedGroupId); - $customer - ->method('setGroupId') - ->willReturnOnConsecutiveCalls(null, $defaultGroupId); -diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -new file mode 100644 -index 0000000000000..107df2c2863ef ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -@@ -0,0 +1,112 @@ -+authorizationMock = $this->createMock(Authorization::class); -+ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ -+ 'authorization' => $this->authorizationMock -+ ]); -+ $this->massScheduleMock = $this->createMock(MassSchedule::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforePublishMass( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $customer = $this->getMockForAbstractClass(CustomerInterface::class); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $entitiesArray = [ -+ [$customer, 'Password1', ''] -+ ]; -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ $this->plugin->beforePublishMass( -+ $this->massScheduleMock, -+ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', -+ $entitiesArray, -+ '', -+ '' -+ ); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json -index 2d76da56bff7d..ff34d423c2da5 100644 ---- a/vendor/magento/module-customer/composer.json -+++ b/vendor/magento/module-customer/composer.json -@@ -29,5 +29,6 @@ - "suggest": { - "magento/module-cookie": "100.4.*", - "magento/module-customer-sample-data": "Sample Data version: 100.4.*", -- "magento/module-webapi": "100.4.*" -+ "magento/module-webapi": "100.4.*", -+ "magento/module-asynchronous-operations": "100.4.*" - }, diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml index 156986b7b4a3c..120a8dda8aece 100644 --- a/vendor/magento/module-customer/etc/di.xml @@ -871,39 +286,6 @@ index 156986b7b4a3c..120a8dda8aece 100644 + /> + - - ---- - ...AsyncRequestCustomerGroupAuthorization.php | 6 +- - app/code/Magento/Quote/etc/webapi.xml | 3 + - .../Rest/Asynchronous/InputParamsResolver.php | 99 ++++++++++++++++++- - .../Quote/Api/GuestCartManagementTest.php | 2 +- - 4 files changed, 104 insertions(+), 6 deletions(-) - -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 5b5c8ce1fc0ca..0aa2b8bfb1d18 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -9,7 +9,6 @@ - namespace Magento\Customer\Plugin; - - use Magento\Customer\Api\Data\CustomerInterface; --use Magento\Framework\App\ObjectManager; - use Magento\Framework\AuthorizationInterface; - use Magento\Framework\Exception\AuthorizationException; - use Magento\AsynchronousOperations\Model\MassSchedule; -@@ -60,6 +59,11 @@ public function beforePublishMass( - string $groupId = null, - string $userId = null - ) { -+ // only apply the plugin on account create. -+ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { -+ return; -+ } -+ - foreach ($entitiesArray as $entityParams) { - foreach ($entityParams as $entity) { - if ($entity instanceof CustomerInterface) { diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml index 79d98968ea198..a7cce5b03a26d 100644 --- a/vendor/magento/module-quote/etc/webapi.xml @@ -919,13 +301,13 @@ index 79d98968ea198..a7cce5b03a26d 100644 diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 8601e5011bda7..93555559ac9a1 100644 +index 8601e5011bda7..2f46685c6b117 100644 --- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,10 +8,12 @@ - + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - + +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\AuthorizationException; @@ -936,7 +318,7 @@ index 8601e5011bda7..93555559ac9a1 100644 use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; @@ -24,6 +26,8 @@ - + /** * This class is responsible for retrieving resolved input data + * @@ -944,19 +326,24 @@ index 8601e5011bda7..93555559ac9a1 100644 */ class InputParamsResolver { -@@ -61,6 +65,11 @@ class InputParamsResolver +@@ -61,6 +65,16 @@ class InputParamsResolver */ private $inputArraySizeLimitValue; - + + /** + * @var MethodsMap + */ + private $methodsMap; ++ ++ /** ++ * @var array ++ */ ++ private array $inputData = []; + /** * Initialize dependencies. * -@@ -72,6 +81,7 @@ class InputParamsResolver +@@ -72,6 +86,7 @@ class InputParamsResolver * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue @@ -964,7 +351,7 @@ index 8601e5011bda7..93555559ac9a1 100644 */ public function __construct( RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( +@@ -81,7 +96,8 @@ public function __construct( RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, bool $isBulk = false, @@ -974,17 +361,17 @@ index 8601e5011bda7..93555559ac9a1 100644 ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( +@@ -92,6 +108,8 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); + $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() + ->get(MethodsMap::class); } - + /** -@@ -113,12 +126,19 @@ public function resolve() - +@@ -113,12 +131,19 @@ public function resolve() + $this->requestValidator->validate(); $webapiResolvedParams = []; + $inputData = $this->getInputData(); @@ -992,7 +379,7 @@ index 8601e5011bda7..93555559ac9a1 100644 $routeServiceClass = $route->getServiceClass(); $routeServiceMethod = $route->getServiceMethod(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - + - foreach ($this->getInputData() as $key => $singleEntityParams) { + $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); + @@ -1004,9 +391,22 @@ index 8601e5011bda7..93555559ac9a1 100644 $webapiResolvedParams[$key] = $this->resolveBulkItemParams( $singleEntityParams, $routeServiceClass, -@@ -142,11 +162,22 @@ public function getInputData() +@@ -136,17 +161,36 @@ public function resolve() + */ + public function getInputData() + { ++ if (!empty($this->inputData)) { ++ return $this->inputData; ++ } ++ + if ($this->isBulk === false) { +- return [$this->inputParamsResolver->getInputData()]; ++ $this->inputData = [$this->inputParamsResolver->getInputData()]; ++ ++ return $this->inputData; + } $inputData = $this->request->getRequestData(); - + $httpMethod = $this->request->getHttpMethod(); - if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { + if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { @@ -1015,7 +415,7 @@ index 8601e5011bda7..93555559ac9a1 100644 } - return $inputData; + -+ return array_map(function ($singleEntityParams) { ++ $this->inputData = array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( @@ -1026,10 +426,12 @@ index 8601e5011bda7..93555559ac9a1 100644 + + return $singleEntityParams; + }, $inputData); ++ ++ return $this->inputData; } - + /** -@@ -179,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s +@@ -179,4 +223,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s { return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } @@ -1094,86 +496,3 @@ index 8601e5011bda7..93555559ac9a1 100644 + } + } } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index ce9e4ee941785..44533303c632d 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -339,7 +339,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); - - ---- - .../Magento/Quote/Api/GuestCartManagementTest.php | 9 ++++++--- - .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 1 + - 2 files changed, 7 insertions(+), 3 deletions(-) - -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index 44533303c632d..e08fe0388cfbe 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -10,10 +10,13 @@ - - class GuestCartManagementTest extends WebapiAbstract - { -- const SERVICE_VERSION = 'V1'; -- const SERVICE_NAME = 'quoteGuestCartManagementV1'; -- const RESOURCE_PATH = '/V1/guest-carts/'; -+ public const SERVICE_VERSION = 'V1'; -+ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; -+ public const RESOURCE_PATH = '/V1/guest-carts/'; - -+ /** -+ * @var array List of created quotes -+ */ - protected $createdQuotes = []; - - /** -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index efc7e669b3605..18ffe842c794c 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -109,3 +109,4 @@ app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/ - app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php - app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php - app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php - - ---- - .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 0aa2b8bfb1d18..295b33d2db14a 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -25,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization - */ - public const ADMIN_RESOURCE = 'Magento_Customer::manage'; - -+ /** -+ * account create topic name -+ * -+ * @var string -+ */ -+ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; -+ - /** - * @var AuthorizationInterface - */ -@@ -60,7 +67,7 @@ public function beforePublishMass( - string $userId = null - ) { - // only apply the plugin on account create. -- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { -+ if ($topic !== self::TOPIC_NAME) { - return; - } - diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch index 5295ddf..30f8eab 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch @@ -1,17 +1,3 @@ ---- - .../Customer/Model/AccountManagement.php | 5 - - .../Customer/Model/AccountManagementApi.php | 132 ++++++ - ...AsyncRequestCustomerGroupAuthorization.php | 78 ++++ - .../Unit/Model/AccountManagementApiTest.php | 421 ++++++++++++++++++ - .../Test/Unit/Model/AccountManagementTest.php | 4 - - ...cRequestCustomerGroupAuthorizationTest.php | 112 +++++ - app/code/Magento/Customer/composer.json | 3 +- - app/code/Magento/Customer/etc/di.xml | 5 + - 8 files changed, 750 insertions(+), 10 deletions(-) - create mode 100644 app/code/Magento/Customer/Plugin/AsyncRequestCustomerGroupAuthorization.php - create mode 100644 app/code/Magento/Customer/Test/Unit/Model/AccountManagementApiTest.php - create mode 100644 app/code/Magento/Customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php - diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php index d70058aef445f..513c0b0717e85 100644 --- a/vendor/magento/module-customer/Model/AccountManagement.php @@ -193,10 +179,10 @@ index 02a05705b57ef..8b4f78ab26c77 100644 } diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php new file mode 100644 -index 0000000000000..5b5c8ce1fc0ca +index 0000000000000..295b33d2db14a --- /dev/null +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -0,0 +1,78 @@ +@@ -0,0 +1,89 @@ +customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); -+ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); -+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); -+ $this->random = $this->createMock(Random::class); -+ $this->validator = $this->createMock(Validator::class); -+ $this->validationResultsInterfaceFactory = $this->createMock( -+ ValidationResultsInterfaceFactory::class -+ ); -+ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); -+ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); -+ $this->customerRegistry = $this->createMock(CustomerRegistry::class); -+ -+ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); -+ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); -+ $this->share = $this->createMock(Share::class); -+ $this->string = $this->createMock(StringUtils::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->transportBuilder = $this->createMock(TransportBuilder::class); -+ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); -+ $this->registry = $this->createMock(Registry::class); -+ $this->customerViewHelper = $this->createMock(View::class); -+ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); -+ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); -+ $this->objectFactory = $this->createMock(DataObjectFactory::class); -+ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); -+ $this->extensibleDataObjectConverter = $this->createMock( -+ ExtensibleDataObjectConverter::class -+ ); -+ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); -+ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) -+ ->onlyMethods(['addData', 'setData']) -+ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); -+ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); -+ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); -+ -+ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) -+ ->disableOriginalConstructor() -+ ->onlyMethods(['create']) -+ ->getMock(); -+ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->authorizationMock = $this->createMock(Authorization::class); -+ $this->objectManagerHelper = new ObjectManagerHelper($this); -+ $this->accountManagement = $this->objectManagerHelper->getObject( -+ AccountManagementApi::class, -+ [ -+ 'customerFactory' => $this->customerFactory, -+ 'eventManager' => $this->manager, -+ 'storeManager' => $this->storeManager, -+ 'mathRandom' => $this->random, -+ 'validator' => $this->validator, -+ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, -+ 'addressRepository' => $this->addressRepository, -+ 'customerMetadataService' => $this->customerMetadata, -+ 'customerRegistry' => $this->customerRegistry, -+ 'logger' => $this->logger, -+ 'encryptor' => $this->encryptor, -+ 'configShare' => $this->share, -+ 'stringHelper' => $this->string, -+ 'customerRepository' => $this->customerRepository, -+ 'scopeConfig' => $this->scopeConfig, -+ 'transportBuilder' => $this->transportBuilder, -+ 'dataProcessor' => $this->dataObjectProcessor, -+ 'registry' => $this->registry, -+ 'customerViewHelper' => $this->customerViewHelper, -+ 'dateTime' => $this->dateTime, -+ 'customerModel' => $this->customer, -+ 'objectFactory' => $this->objectFactory, -+ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, -+ 'dateTimeFactory' => $this->dateTimeFactory, -+ 'accountConfirmation' => $this->accountConfirmation, -+ 'sessionManager' => $this->sessionManager, -+ 'saveHandler' => $this->saveHandler, -+ 'visitorCollectionFactory' => $this->visitorCollectionFactory, -+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, -+ 'addressRegistry' => $this->addressRegistryMock, -+ 'allowedCountriesReader' => $this->allowedCountriesReader, -+ 'authorization' => $this->authorizationMock -+ ] -+ ); -+ $this->accountManagementMock = $this->createMock(AccountManagement::class); -+ -+ $this->storeMock = $this->getMockBuilder( -+ StoreInterface::class -+ )->disableOriginalConstructor() -+ ->getMock(); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @throws LocalizedException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforeCreateAccount( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ -+ $customer = $this->getMockBuilder(CustomerInterface::class) -+ ->addMethods(['setData']) -+ ->getMockForAbstractClass(); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $customer->method('getWebsiteId')->willReturn(2); -+ $customer->method('getStoreId')->willReturn(1); -+ $customer->method('setData')->willReturn(1); -+ -+ $this->customerRepository->method('get')->willReturn($customer); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $this->customerRepository->method('save')->willReturn($customer); -+ -+ if (!$willThrowException) { -+ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); -+ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); -+ $this->random->method('getUniqueHash')->willReturn('testabc'); -+ $date = $this->getMockBuilder(\DateTime::class) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory->expects(static::once()) -+ ->method('create') -+ ->willReturn($date); -+ $date->expects(static::once()) -+ ->method('format') -+ ->with('Y-m-d H:i:s') -+ ->willReturn('2015-01-01 00:00:00'); -+ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); -+ $this->storeManager->method('getStores') -+ ->willReturn([$this->storeMock]); -+ } -+ $this->accountManagement->createAccount($customer); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -index 8ff6a8585212f..cbe0a18e4b178 100644 ---- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - $defaultGroupId = 1; -- $requestedGroupId = 3; - - $datetime = $this->prepareDateTimeFactory(); - -@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void - return null; - } - })); -- $customer->expects($this->atLeastOnce()) -- ->method('getGroupId') -- ->willReturn($requestedGroupId); - $customer - ->method('setGroupId') - ->willReturnOnConsecutiveCalls(null, $defaultGroupId); -diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -new file mode 100644 -index 0000000000000..107df2c2863ef ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -@@ -0,0 +1,112 @@ -+authorizationMock = $this->createMock(Authorization::class); -+ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ -+ 'authorization' => $this->authorizationMock -+ ]); -+ $this->massScheduleMock = $this->createMock(MassSchedule::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforePublishMass( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $customer = $this->getMockForAbstractClass(CustomerInterface::class); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $entitiesArray = [ -+ [$customer, 'Password1', ''] -+ ]; -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ $this->plugin->beforePublishMass( -+ $this->massScheduleMock, -+ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', -+ $entitiesArray, -+ '', -+ '' -+ ); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json -index 2d76da56bff7d..ff34d423c2da5 100644 ---- a/vendor/magento/module-customer/composer.json -+++ b/vendor/magento/module-customer/composer.json -@@ -35,5 +35,6 @@ - "suggest": { - "magento/module-cookie": "100.4.*", - "magento/module-customer-sample-data": "Sample Data version: 100.4.*", -- "magento/module-webapi": "100.4.*" -+ "magento/module-webapi": "100.4.*", -+ "magento/module-asynchronous-operations": "100.4.*" - }, - diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml index 31b79935ad9ab..4cda16e121c97 100644 --- a/vendor/magento/module-customer/etc/di.xml @@ -869,38 +286,6 @@ index 31b79935ad9ab..4cda16e121c97 100644 + /> + - ---- - ...AsyncRequestCustomerGroupAuthorization.php | 6 +- - app/code/Magento/Quote/etc/webapi.xml | 3 + - .../Rest/Asynchronous/InputParamsResolver.php | 96 ++++++++++++++++++- - .../Quote/Api/GuestCartManagementTest.php | 2 +- - 4 files changed, 102 insertions(+), 5 deletions(-) - -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 5b5c8ce1fc0ca..0aa2b8bfb1d18 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -9,7 +9,6 @@ - namespace Magento\Customer\Plugin; - - use Magento\Customer\Api\Data\CustomerInterface; --use Magento\Framework\App\ObjectManager; - use Magento\Framework\AuthorizationInterface; - use Magento\Framework\Exception\AuthorizationException; - use Magento\AsynchronousOperations\Model\MassSchedule; -@@ -60,6 +59,11 @@ public function beforePublishMass( - string $groupId = null, - string $userId = null - ) { -+ // only apply the plugin on account create. -+ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { -+ return; -+ } -+ - foreach ($entitiesArray as $entityParams) { - foreach ($entityParams as $entity) { - if ($entity instanceof CustomerInterface) { diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml index 79d98968ea198..a7cce5b03a26d 100644 --- a/vendor/magento/module-quote/etc/webapi.xml @@ -916,7 +301,7 @@ index 79d98968ea198..a7cce5b03a26d 100644 diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc5..93555559ac9a1 100644 +index 6718087888bc5..2f46685c6b117 100644 --- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,10 +8,12 @@ @@ -941,7 +326,7 @@ index 6718087888bc5..93555559ac9a1 100644 */ class InputParamsResolver { -@@ -61,6 +65,11 @@ class InputParamsResolver +@@ -61,6 +65,16 @@ class InputParamsResolver */ private $inputArraySizeLimitValue; @@ -949,11 +334,16 @@ index 6718087888bc5..93555559ac9a1 100644 + * @var MethodsMap + */ + private $methodsMap; ++ ++ /** ++ * @var array ++ */ ++ private array $inputData = []; + /** * Initialize dependencies. * -@@ -72,6 +81,7 @@ class InputParamsResolver +@@ -72,6 +86,7 @@ class InputParamsResolver * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue @@ -961,7 +351,7 @@ index 6718087888bc5..93555559ac9a1 100644 */ public function __construct( RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( +@@ -81,7 +96,8 @@ public function __construct( RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, bool $isBulk = false, @@ -971,7 +361,7 @@ index 6718087888bc5..93555559ac9a1 100644 ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( +@@ -92,6 +108,8 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); @@ -980,7 +370,7 @@ index 6718087888bc5..93555559ac9a1 100644 } /** -@@ -119,7 +132,13 @@ public function resolve() +@@ -119,7 +137,13 @@ public function resolve() $routeServiceMethod = $route->getServiceMethod(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); @@ -994,7 +384,20 @@ index 6718087888bc5..93555559ac9a1 100644 $webapiResolvedParams[$key] = $this->resolveBulkItemParams( $singleEntityParams, $routeServiceClass, -@@ -143,11 +162,22 @@ public function getInputData() +@@ -137,17 +161,36 @@ public function resolve() + */ + public function getInputData() + { ++ if (!empty($this->inputData)) { ++ return $this->inputData; ++ } ++ + if ($this->isBulk === false) { +- return [$this->inputParamsResolver->getInputData()]; ++ $this->inputData = [$this->inputParamsResolver->getInputData()]; ++ ++ return $this->inputData; + } $inputData = $this->request->getRequestData(); $httpMethod = $this->request->getHttpMethod(); @@ -1005,7 +408,7 @@ index 6718087888bc5..93555559ac9a1 100644 } - return $inputData; + -+ return array_map(function ($singleEntityParams) { ++ $this->inputData = array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( @@ -1016,10 +419,12 @@ index 6718087888bc5..93555559ac9a1 100644 + + return $singleEntityParams; + }, $inputData); ++ ++ return $this->inputData; } /** -@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s +@@ -180,4 +223,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s { return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } @@ -1084,87 +489,3 @@ index 6718087888bc5..93555559ac9a1 100644 + } + } } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index ce9e4ee941785..44533303c632d 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -339,7 +339,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); - ---- - .../Magento/Quote/Api/GuestCartManagementTest.php | 9 ++++++--- - .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 3 ++- - 2 files changed, 8 insertions(+), 4 deletions(-) - -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index 44533303c632d..e08fe0388cfbe 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -10,10 +10,13 @@ - - class GuestCartManagementTest extends WebapiAbstract - { -- const SERVICE_VERSION = 'V1'; -- const SERVICE_NAME = 'quoteGuestCartManagementV1'; -- const RESOURCE_PATH = '/V1/guest-carts/'; -+ public const SERVICE_VERSION = 'V1'; -+ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; -+ public const RESOURCE_PATH = '/V1/guest-carts/'; - -+ /** -+ * @var array List of created quotes -+ */ - protected $createdQuotes = []; - - /** -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index 9991dd4e05fe7..dc98670bd000f 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -110,4 +110,5 @@ app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php - app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php - app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml - app/code/Magento/GoogleGtag --app/code/Magento/AdminAdobeIms/Observer/AuthObserver -\ No newline at end of file -+app/code/Magento/AdminAdobeIms/Observer/AuthObserver -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php - ---- - .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 0aa2b8bfb1d18..295b33d2db14a 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -25,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization - */ - public const ADMIN_RESOURCE = 'Magento_Customer::manage'; - -+ /** -+ * account create topic name -+ * -+ * @var string -+ */ -+ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; -+ - /** - * @var AuthorizationInterface - */ -@@ -60,7 +67,7 @@ public function beforePublishMass( - string $userId = null - ) { - // only apply the plugin on account create. -- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { -+ if ($topic !== self::TOPIC_NAME) { - return; - } - diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch index c78eefe..b1dcf38 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch @@ -1,219 +1,3 @@ - vendor/magento/module-quote/etc/webapi.xml | 3 + - .../Rest/Asynchronous/InputParamsResolver.php | 96 ++++++++++++++++++- - .../Quote/Api/GuestCartManagementTest.php | 2 +- - 3 files changed, 97 insertions(+), 4 deletions(-) - -diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea198..a7cce5b03a26d 100644 ---- a/vendor/magento/module-quote/etc/webapi.xml -+++ b/vendor/magento/module-quote/etc/webapi.xml -@@ -98,6 +98,9 @@ - - - -+ -+ %customer_id% -+ - - - -diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc5..93555559ac9a1 100644 ---- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -+++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -@@ -8,10 +8,12 @@ - - namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - -+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\Rest\Request as RestRequest; - use Magento\Framework\Webapi\ServiceInputProcessor; -@@ -24,6 +26,8 @@ - - /** - * This class is responsible for retrieving resolved input data -+ * -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class InputParamsResolver - { -@@ -61,6 +65,11 @@ class InputParamsResolver - */ - private $inputArraySizeLimitValue; - -+ /** -+ * @var MethodsMap -+ */ -+ private $methodsMap; -+ - /** - * Initialize dependencies. - * -@@ -72,6 +81,7 @@ class InputParamsResolver - * @param WebapiInputParamsResolver $inputParamsResolver - * @param bool $isBulk - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue -+ * @param MethodsMap|null $methodsMap - */ - public function __construct( - RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( - RequestValidator $requestValidator, - WebapiInputParamsResolver $inputParamsResolver, - bool $isBulk = false, -- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null -+ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, -+ ?MethodsMap $methodsMap = null - ) { - $this->request = $request; - $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( - $this->isBulk = $isBulk; - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); -+ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() -+ ->get(MethodsMap::class); - } - - /** -@@ -119,7 +132,13 @@ public function resolve() - $routeServiceMethod = $route->getServiceMethod(); - $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - -+ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); -+ - foreach ($inputData as $key => $singleEntityParams) { -+ if (!is_array($singleEntityParams)) { -+ continue; -+ } -+ - $webapiResolvedParams[$key] = $this->resolveBulkItemParams( - $singleEntityParams, - $routeServiceClass, -@@ -143,11 +162,22 @@ public function getInputData() - $inputData = $this->request->getRequestData(); - - $httpMethod = $this->request->getHttpMethod(); -- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { -+ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { - $requestBodyParams = $this->request->getBodyParams(); - $inputData = array_merge($requestBodyParams, $inputData); - } -- return $inputData; -+ -+ return array_map(function ($singleEntityParams) { -+ if (is_array($singleEntityParams)) { -+ $singleEntityParams = $this->filterInputData($singleEntityParams); -+ $singleEntityParams = $this->paramsOverrider->override( -+ $singleEntityParams, -+ $this->getRoute()->getParameters() -+ ); -+ } -+ -+ return $singleEntityParams; -+ }, $inputData); - } - - /** -@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s - { - return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); - } -+ -+ /** -+ * Validates InputData -+ * -+ * @param array $inputData -+ * @return array -+ */ -+ private function filterInputData(array $inputData): array -+ { -+ $result = []; -+ -+ $data = array_filter($inputData, function ($k) use (&$result) { -+ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; -+ return !isset($result[$key]) && ($result[$key] = true); -+ }, ARRAY_FILTER_USE_KEY); -+ -+ return array_map(function ($value) { -+ return is_array($value) ? $this->filterInputData($value) : $value; -+ }, $data); -+ } -+ -+ /** -+ * Validate that parameters are really used in the current request. -+ * -+ * @param string $serviceClassName -+ * @param string $serviceMethodName -+ * @param array $paramOverriders -+ */ -+ private function validateParameters( -+ string $serviceClassName, -+ string $serviceMethodName, -+ array $paramOverriders -+ ): void { -+ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); -+ foreach ($paramOverriders as $key => $param) { -+ $arrayKeys = explode('.', $param ?? ''); -+ $value = array_shift($arrayKeys); -+ -+ foreach ($methodParams as $serviceMethodParam) { -+ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; -+ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; -+ -+ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); -+ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { -+ if (count($arrayKeys) > 0) { -+ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); -+ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); -+ } -+ unset($paramOverriders[$key]); -+ break; -+ } -+ } -+ } -+ -+ if (!empty($paramOverriders)) { -+ $message = 'The current request does not expect the next parameters: ' -+ . implode(', ', $paramOverriders); -+ throw new \UnexpectedValueException(__($message)->__toString()); -+ } -+ } - } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index ce9e4ee941785..44533303c632d 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -339,7 +339,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); - ---- - .../Customer/Model/AccountManagement.php | 5 - - .../Customer/Model/AccountManagementApi.php | 132 ++++++ - ...AsyncRequestCustomerGroupAuthorization.php | 78 ++++ - .../Unit/Model/AccountManagementApiTest.php | 421 ++++++++++++++++++ - .../Test/Unit/Model/AccountManagementTest.php | 4 - - ...cRequestCustomerGroupAuthorizationTest.php | 112 +++++ - app/code/Magento/Customer/composer.json | 3 +- - app/code/Magento/Customer/etc/di.xml | 5 + - 8 files changed, 750 insertions(+), 10 deletions(-) - create mode 100644 app/code/Magento/Customer/Plugin/AsyncRequestCustomerGroupAuthorization.php - create mode 100644 app/code/Magento/Customer/Test/Unit/Model/AccountManagementApiTest.php - create mode 100644 app/code/Magento/Customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php - diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php index 702b6aeb68653..bfebafeb0330b 100644 --- a/vendor/magento/module-customer/Model/AccountManagement.php @@ -395,10 +179,10 @@ index 02a05705b57ef..8b4f78ab26c77 100644 } diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php new file mode 100644 -index 0000000000000..5b5c8ce1fc0ca +index 0000000000000..cdda3016694ca --- /dev/null +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -0,0 +1,78 @@ +@@ -0,0 +1,90 @@ + + + ++ ++ ++ + +diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml +index 79d98968ea198..a7cce5b03a26d 100644 +--- a/vendor/magento/module-quote/etc/webapi.xml ++++ b/vendor/magento/module-quote/etc/webapi.xml +@@ -98,6 +98,9 @@ + + + ++ ++ %customer_id% ++ + + + +diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +index 6718087888bc5..2f46685c6b117 100644 +--- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php ++++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +@@ -8,10 +8,12 @@ + + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; + ++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\Rest\Request as RestRequest; + use Magento\Framework\Webapi\ServiceInputProcessor; +@@ -24,6 +26,8 @@ + + /** + * This class is responsible for retrieving resolved input data ++ * ++ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class InputParamsResolver + { +@@ -61,6 +65,16 @@ class InputParamsResolver + */ + private $inputArraySizeLimitValue; + ++ /** ++ * @var MethodsMap ++ */ ++ private $methodsMap; + + /** -+ * @var Validator|MockObject ++ * @var array + */ -+ private $validator; ++ private array $inputData = []; + -+ /** -+ * @var ValidationResultsInterfaceFactory|MockObject -+ */ -+ private $validationResultsInterfaceFactory; -+ -+ /** -+ * @var AddressRepositoryInterface|MockObject -+ */ -+ private $addressRepository; -+ -+ /** -+ * @var CustomerMetadataInterface|MockObject -+ */ -+ private $customerMetadata; -+ -+ /** -+ * @var CustomerRegistry|MockObject -+ */ -+ private $customerRegistry; -+ -+ /** -+ * @var LoggerInterface|MockObject -+ */ -+ private $logger; -+ -+ /** -+ * @var EncryptorInterface|MockObject -+ */ -+ private $encryptor; -+ -+ /** -+ * @var Share|MockObject -+ */ -+ private $share; -+ -+ /** -+ * @var StringUtils|MockObject -+ */ -+ private $string; -+ -+ /** -+ * @var CustomerRepositoryInterface|MockObject -+ */ -+ private $customerRepository; -+ -+ /** -+ * @var ScopeConfigInterface|MockObject -+ */ -+ private $scopeConfig; -+ -+ /** -+ * @var TransportBuilder|MockObject -+ */ -+ private $transportBuilder; -+ -+ /** -+ * @var DataObjectProcessor|MockObject -+ */ -+ private $dataObjectProcessor; -+ -+ /** -+ * @var Registry|MockObject -+ */ -+ private $registry; -+ -+ /** -+ * @var View|MockObject -+ */ -+ private $customerViewHelper; -+ -+ /** -+ * @var \Magento\Framework\Stdlib\DateTime|MockObject -+ */ -+ private $dateTime; -+ -+ /** -+ * @var \Magento\Customer\Model\Customer|MockObject -+ */ -+ private $customer; -+ -+ /** -+ * @var DataObjectFactory|MockObject -+ */ -+ private $objectFactory; -+ -+ /** -+ * @var ExtensibleDataObjectConverter|MockObject -+ */ -+ private $extensibleDataObjectConverter; -+ -+ /** -+ * @var DateTimeFactory|MockObject -+ */ -+ private $dateTimeFactory; -+ -+ /** -+ * @var AccountConfirmation|MockObject -+ */ -+ private $accountConfirmation; -+ -+ /** -+ * @var MockObject|SessionManagerInterface -+ */ -+ private $sessionManager; -+ -+ /** -+ * @var MockObject|CollectionFactory -+ */ -+ private $visitorCollectionFactory; + /** + * Initialize dependencies. + * +@@ -72,6 +86,7 @@ class InputParamsResolver + * @param WebapiInputParamsResolver $inputParamsResolver + * @param bool $isBulk + * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue ++ * @param MethodsMap|null $methodsMap + */ + public function __construct( + RestRequest $request, +@@ -81,7 +96,8 @@ public function __construct( + RequestValidator $requestValidator, + WebapiInputParamsResolver $inputParamsResolver, + bool $isBulk = false, +- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null ++ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, ++ ?MethodsMap $methodsMap = null + ) { + $this->request = $request; + $this->paramsOverrider = $paramsOverrider; +@@ -92,6 +108,8 @@ public function __construct( + $this->isBulk = $isBulk; + $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() + ->get(InputArraySizeLimitValue::class); ++ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() ++ ->get(MethodsMap::class); + } + + /** +@@ -119,7 +137,13 @@ public function resolve() + $routeServiceMethod = $route->getServiceMethod(); + $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); + ++ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); + -+ /** -+ * @var MockObject|SaveHandlerInterface -+ */ -+ private $saveHandler; + foreach ($inputData as $key => $singleEntityParams) { ++ if (!is_array($singleEntityParams)) { ++ continue; ++ } + -+ /** -+ * @var MockObject|AddressRegistry -+ */ -+ private $addressRegistryMock; + $webapiResolvedParams[$key] = $this->resolveBulkItemParams( + $singleEntityParams, + $routeServiceClass, +@@ -137,17 +161,36 @@ public function resolve() + */ + public function getInputData() + { ++ if (!empty($this->inputData)) { ++ return $this->inputData; ++ } + -+ /** -+ * @var MockObject|SearchCriteriaBuilder -+ */ -+ private $searchCriteriaBuilderMock; + if ($this->isBulk === false) { +- return [$this->inputParamsResolver->getInputData()]; ++ $this->inputData = [$this->inputParamsResolver->getInputData()]; + -+ /** -+ * @var AllowedCountries|MockObject -+ */ -+ private $allowedCountriesReader; ++ return $this->inputData; + } + $inputData = $this->request->getRequestData(); + + $httpMethod = $this->request->getHttpMethod(); +- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { ++ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { + $requestBodyParams = $this->request->getBodyParams(); + $inputData = array_merge($requestBodyParams, $inputData); + } +- return $inputData; + -+ /** -+ * @var Authorization|MockObject -+ */ -+ private $authorizationMock; ++ $this->inputData = array_map(function ($singleEntityParams) { ++ if (is_array($singleEntityParams)) { ++ $singleEntityParams = $this->filterInputData($singleEntityParams); ++ $singleEntityParams = $this->paramsOverrider->override( ++ $singleEntityParams, ++ $this->getRoute()->getParameters() ++ ); ++ } + -+ /** -+ * @var CustomerSecure|MockObject -+ */ -+ private $customerSecure; ++ return $singleEntityParams; ++ }, $inputData); + -+ /** -+ * @var StoreInterface|MockObject -+ */ -+ private $storeMock; ++ return $this->inputData; + } + + /** +@@ -180,4 +223,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s + { + return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); + } + + /** -+ * @inheritDoc -+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength) ++ * Validates InputData ++ * ++ * @param array $inputData ++ * @return array + */ -+ protected function setUp(): void ++ private function filterInputData(array $inputData): array + { -+ $this->customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); -+ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); -+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); -+ $this->random = $this->createMock(Random::class); -+ $this->validator = $this->createMock(Validator::class); -+ $this->validationResultsInterfaceFactory = $this->createMock( -+ ValidationResultsInterfaceFactory::class -+ ); -+ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); -+ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); -+ $this->customerRegistry = $this->createMock(CustomerRegistry::class); -+ -+ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); -+ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); -+ $this->share = $this->createMock(Share::class); -+ $this->string = $this->createMock(StringUtils::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->transportBuilder = $this->createMock(TransportBuilder::class); -+ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); -+ $this->registry = $this->createMock(Registry::class); -+ $this->customerViewHelper = $this->createMock(View::class); -+ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); -+ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); -+ $this->objectFactory = $this->createMock(DataObjectFactory::class); -+ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); -+ $this->extensibleDataObjectConverter = $this->createMock( -+ ExtensibleDataObjectConverter::class -+ ); -+ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); -+ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) -+ ->onlyMethods(['addData', 'setData']) -+ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); -+ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); -+ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); ++ $result = []; + -+ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) -+ ->disableOriginalConstructor() -+ ->onlyMethods(['create']) -+ ->getMock(); -+ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->authorizationMock = $this->createMock(Authorization::class); -+ $this->objectManagerHelper = new ObjectManagerHelper($this); -+ $this->accountManagement = $this->objectManagerHelper->getObject( -+ AccountManagementApi::class, -+ [ -+ 'customerFactory' => $this->customerFactory, -+ 'eventManager' => $this->manager, -+ 'storeManager' => $this->storeManager, -+ 'mathRandom' => $this->random, -+ 'validator' => $this->validator, -+ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, -+ 'addressRepository' => $this->addressRepository, -+ 'customerMetadataService' => $this->customerMetadata, -+ 'customerRegistry' => $this->customerRegistry, -+ 'logger' => $this->logger, -+ 'encryptor' => $this->encryptor, -+ 'configShare' => $this->share, -+ 'stringHelper' => $this->string, -+ 'customerRepository' => $this->customerRepository, -+ 'scopeConfig' => $this->scopeConfig, -+ 'transportBuilder' => $this->transportBuilder, -+ 'dataProcessor' => $this->dataObjectProcessor, -+ 'registry' => $this->registry, -+ 'customerViewHelper' => $this->customerViewHelper, -+ 'dateTime' => $this->dateTime, -+ 'customerModel' => $this->customer, -+ 'objectFactory' => $this->objectFactory, -+ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, -+ 'dateTimeFactory' => $this->dateTimeFactory, -+ 'accountConfirmation' => $this->accountConfirmation, -+ 'sessionManager' => $this->sessionManager, -+ 'saveHandler' => $this->saveHandler, -+ 'visitorCollectionFactory' => $this->visitorCollectionFactory, -+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, -+ 'addressRegistry' => $this->addressRegistryMock, -+ 'allowedCountriesReader' => $this->allowedCountriesReader, -+ 'authorization' => $this->authorizationMock -+ ] -+ ); -+ $this->accountManagementMock = $this->createMock(AccountManagement::class); ++ $data = array_filter($inputData, function ($k) use (&$result) { ++ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; ++ return !isset($result[$key]) && ($result[$key] = true); ++ }, ARRAY_FILTER_USE_KEY); + -+ $this->storeMock = $this->getMockBuilder( -+ StoreInterface::class -+ )->disableOriginalConstructor() -+ ->getMock(); ++ return array_map(function ($value) { ++ return is_array($value) ? $this->filterInputData($value) : $value; ++ }, $data); + } + + /** -+ * Verify that only authorized request will be able to change groupId ++ * Validate that parameters are really used in the current request. + * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @throws LocalizedException -+ * @dataProvider customerDataProvider ++ * @param string $serviceClassName ++ * @param string $serviceMethodName ++ * @param array $paramOverriders + */ -+ public function testBeforeCreateAccount( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException ++ private function validateParameters( ++ string $serviceClassName, ++ string $serviceMethodName, ++ array $paramOverriders + ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ -+ $customer = $this->getMockBuilder(CustomerInterface::class) -+ ->addMethods(['setData']) -+ ->getMockForAbstractClass(); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $customer->method('getWebsiteId')->willReturn(2); -+ $customer->method('getStoreId')->willReturn(1); -+ $customer->method('setData')->willReturn(1); ++ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); ++ foreach ($paramOverriders as $key => $param) { ++ $arrayKeys = explode('.', $param ?? ''); ++ $value = array_shift($arrayKeys); + -+ $this->customerRepository->method('get')->willReturn($customer); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $this->customerRepository->method('save')->willReturn($customer); ++ foreach ($methodParams as $serviceMethodParam) { ++ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; ++ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; + -+ if (!$willThrowException) { -+ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); -+ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); -+ $this->random->method('getUniqueHash')->willReturn('testabc'); -+ $date = $this->getMockBuilder(\DateTime::class) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory->expects(static::once()) -+ ->method('create') -+ ->willReturn($date); -+ $date->expects(static::once()) -+ ->method('format') -+ ->with('Y-m-d H:i:s') -+ ->willReturn('2015-01-01 00:00:00'); -+ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); -+ $this->storeManager->method('getStores') -+ ->willReturn([$this->storeMock]); ++ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); ++ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { ++ if (count($arrayKeys) > 0) { ++ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); ++ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); ++ } ++ unset($paramOverriders[$key]); ++ break; ++ } ++ } + } -+ $this->accountManagement->createAccount($customer); -+ } + -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -index 9e68d53fd5949..e2b507f6fe37d 100644 ---- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - $defaultGroupId = 1; -- $requestedGroupId = 3; - - $datetime = $this->prepareDateTimeFactory(); - -@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void - return null; - } - })); -- $customer->expects($this->atLeastOnce()) -- ->method('getGroupId') -- ->willReturn($requestedGroupId); - $customer - ->method('setGroupId') - ->willReturnOnConsecutiveCalls(null, $defaultGroupId); -diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -new file mode 100644 -index 0000000000000..107df2c2863ef ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -@@ -0,0 +1,112 @@ -+authorizationMock = $this->createMock(Authorization::class); -+ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ -+ 'authorization' => $this->authorizationMock -+ ]); -+ $this->massScheduleMock = $this->createMock(MassSchedule::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforePublishMass( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); ++ if (!empty($paramOverriders)) { ++ $message = 'The current request does not expect the next parameters: ' ++ . implode(', ', $paramOverriders); ++ throw new \UnexpectedValueException(__($message)->__toString()); + } -+ $customer = $this->getMockForAbstractClass(CustomerInterface::class); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $entitiesArray = [ -+ [$customer, 'Password1', ''] -+ ]; -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ $this->plugin->beforePublishMass( -+ $this->massScheduleMock, -+ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', -+ $entitiesArray, -+ '', -+ '' -+ ); + } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json -index ef2047644759b..39c82c20f2ec8 100644 ---- a/vendor/magento/module-customer/composer.json -+++ b/vendor/magento/module-customer/composer.json -@@ -29,5 +29,6 @@ - "suggest": { - "magento/module-cookie": "100.4.*", - "magento/module-customer-sample-data": "Sample Data version: 100.4.*", -- "magento/module-webapi": "100.4.*" -+ "magento/module-webapi": "100.4.*", -+ "magento/module-asynchronous-operations": "100.4.*" - }, -diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml -index b178f51f89199..96fd4b86be702 100644 ---- a/vendor/magento/module-customer/etc/di.xml -+++ b/vendor/magento/module-customer/etc/di.xml -@@ -585,4 +585,9 @@ - - - -+ -+ -+ - - ---- - .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 5b5c8ce1fc0ca..d19c48a52a25a 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -60,6 +60,11 @@ public function beforePublishMass( - string $groupId = null, - string $userId = null - ) { -+ // only apply the plugin on account create. -+ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { -+ return; -+ } -+ - foreach ($entitiesArray as $entityParams) { - foreach ($entityParams as $entity) { - if ($entity instanceof CustomerInterface) { - ---- - .../Magento/Quote/Api/GuestCartManagementTest.php | 9 ++++++--- - .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 1 + - 2 files changed, 7 insertions(+), 3 deletions(-) - -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index 44533303c632d..e08fe0388cfbe 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -10,10 +10,13 @@ - - class GuestCartManagementTest extends WebapiAbstract - { -- const SERVICE_VERSION = 'V1'; -- const SERVICE_NAME = 'quoteGuestCartManagementV1'; -- const RESOURCE_PATH = '/V1/guest-carts/'; -+ public const SERVICE_VERSION = 'V1'; -+ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; -+ public const RESOURCE_PATH = '/V1/guest-carts/'; - -+ /** -+ * @var array List of created quotes -+ */ - protected $createdQuotes = []; - - /** -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index 80fe4ec247a64..50285d7492e86 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed - app/code/Magento/GoogleGtag - app/code/Magento/AdminAdobeIms/Observer/AuthObserver - app/code/Magento/OpenSearch/SearchAdapter/Adapter -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php - ---- - .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index d19c48a52a25a..cdda3016694ca 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -26,6 +26,13 @@ class AsyncRequestCustomerGroupAuthorization - */ - public const ADMIN_RESOURCE = 'Magento_Customer::manage'; - -+ /** -+ * account create topic name -+ * -+ * @var string -+ */ -+ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; -+ - /** - * @var AuthorizationInterface - */ -@@ -61,7 +68,7 @@ public function beforePublishMass( - string $userId = null - ) { - // only apply the plugin on account create. -- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { -+ if ($topic !== self::TOPIC_NAME) { - return; - } - + } diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch index 1ab7c79..507e252 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch @@ -1,12 +1,5 @@ ---- - ...AsyncRequestCustomerGroupAuthorization.php | 6 +- - app/code/Magento/Quote/etc/webapi.xml | 3 + - .../Rest/Asynchronous/InputParamsResolver.php | 96 ++++++++++++++++++- - .../Quote/Api/GuestCartManagementTest.php | 2 +- - 4 files changed, 102 insertions(+), 5 deletions(-) - diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 5b5c8ce1fc0ca..0aa2b8bfb1d18 100644 +index 5b5c8ce1fc0ca..295b33d2db14a 100644 --- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php @@ -9,7 +9,6 @@ @@ -17,12 +10,26 @@ index 5b5c8ce1fc0ca..0aa2b8bfb1d18 100644 use Magento\Framework\AuthorizationInterface; use Magento\Framework\Exception\AuthorizationException; use Magento\AsynchronousOperations\Model\MassSchedule; -@@ -60,6 +59,11 @@ public function beforePublishMass( +@@ -26,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization + */ + public const ADMIN_RESOURCE = 'Magento_Customer::manage'; + ++ /** ++ * account create topic name ++ * ++ * @var string ++ */ ++ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; ++ + /** + * @var AuthorizationInterface + */ +@@ -60,6 +66,11 @@ public function beforePublishMass( string $groupId = null, string $userId = null ) { + // only apply the plugin on account create. -+ if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { ++ if ($topic !== self::TOPIC_NAME) { + return; + } + @@ -44,7 +51,7 @@ index 79d98968ea198..a7cce5b03a26d 100644 diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc5..93555559ac9a1 100644 +index 6718087888bc5..7be0b259c251e 100644 --- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,10 +8,12 @@ @@ -69,7 +76,7 @@ index 6718087888bc5..93555559ac9a1 100644 */ class InputParamsResolver { -@@ -61,6 +65,11 @@ class InputParamsResolver +@@ -61,6 +65,16 @@ class InputParamsResolver */ private $inputArraySizeLimitValue; @@ -77,11 +84,16 @@ index 6718087888bc5..93555559ac9a1 100644 + * @var MethodsMap + */ + private $methodsMap; ++ ++ /** ++ * @var array ++ */ ++ private array $inputData = []; + /** * Initialize dependencies. * -@@ -72,6 +81,7 @@ class InputParamsResolver +@@ -72,6 +86,7 @@ class InputParamsResolver * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue @@ -89,7 +101,7 @@ index 6718087888bc5..93555559ac9a1 100644 */ public function __construct( RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( +@@ -81,7 +96,8 @@ public function __construct( RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, bool $isBulk = false, @@ -99,7 +111,7 @@ index 6718087888bc5..93555559ac9a1 100644 ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( +@@ -92,6 +108,8 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); @@ -108,7 +120,7 @@ index 6718087888bc5..93555559ac9a1 100644 } /** -@@ -119,7 +132,13 @@ public function resolve() +@@ -119,7 +137,13 @@ public function resolve() $routeServiceMethod = $route->getServiceMethod(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); @@ -122,7 +134,20 @@ index 6718087888bc5..93555559ac9a1 100644 $webapiResolvedParams[$key] = $this->resolveBulkItemParams( $singleEntityParams, $routeServiceClass, -@@ -143,11 +162,22 @@ public function getInputData() +@@ -137,17 +161,36 @@ public function resolve() + */ + public function getInputData() + { ++ if (!empty($this->inputData)) { ++ return $this->inputData; ++ } ++ + if ($this->isBulk === false) { +- return [$this->inputParamsResolver->getInputData()]; ++ $this->inputData = [$this->inputParamsResolver->getInputData()]; ++ ++ return $this->inputData; + } $inputData = $this->request->getRequestData(); $httpMethod = $this->request->getHttpMethod(); @@ -133,7 +158,7 @@ index 6718087888bc5..93555559ac9a1 100644 } - return $inputData; + -+ return array_map(function ($singleEntityParams) { ++ $this->inputData = array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( @@ -144,10 +169,12 @@ index 6718087888bc5..93555559ac9a1 100644 + + return $singleEntityParams; + }, $inputData); ++ ++ return $this->inputData; } /** -@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s +@@ -180,4 +223,65 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s { return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } @@ -184,6 +211,7 @@ index 6718087888bc5..93555559ac9a1 100644 + string $serviceMethodName, + array $paramOverriders + ): void { ++ //phpcs:ignore CopyPaste + $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); + foreach ($paramOverriders as $key => $param) { + $arrayKeys = explode('.', $param ?? ''); @@ -212,75 +240,3 @@ index 6718087888bc5..93555559ac9a1 100644 + } + } } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index 68cc2c2b2315d..6f08b21f38127 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -354,7 +354,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); - ---- - .../Controller/Rest/Asynchronous/InputParamsResolver.php | 1 + - .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 93555559ac9a1..6e159eaddf162 100644 ---- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -+++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -@@ -243,6 +243,7 @@ private function validateParameters( - string $serviceMethodName, - array $paramOverriders - ): void { -+ //phpcs:ignore CopyPaste - $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); - foreach ($paramOverriders as $key => $param) { - $arrayKeys = explode('.', $param ?? ''); -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index 08ba4bba28c62..c9d07aa2abedf 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed - app/code/Magento/GoogleGtag - app/code/Magento/AdminAdobeIms/Observer/AuthObserver - app/code/Magento/OpenSearch/SearchAdapter/Adapter -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php - ---- - .../Plugin/AsyncRequestCustomerGroupAuthorization.php | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 0aa2b8bfb1d18..295b33d2db14a 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -25,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization - */ - public const ADMIN_RESOURCE = 'Magento_Customer::manage'; - -+ /** -+ * account create topic name -+ * -+ * @var string -+ */ -+ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; -+ - /** - * @var AuthorizationInterface - */ -@@ -60,7 +67,7 @@ public function beforePublishMass( - string $userId = null - ) { - // only apply the plugin on account create. -- if ($topic !== 'async.magento.customer.api.accountmanagementinterface.createaccount.post') { -+ if ($topic !== self::TOPIC_NAME) { - return; - } - From 1f823b4d7b4a7299837370da7d8aa0097d6835d0 Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Tue, 8 Apr 2025 19:39:01 +0530 Subject: [PATCH 68/82] MCLOUD-13240 : Patch for CVE-2025-24434 - Improve-web-api-async --- ...5_24434_improve_web_api_async__2.4.4.patch | 692 +++++++++++++++-- ...5_24434_improve_web_api_async__2.4.5.patch | 693 ++++++++++++++++-- ...5_24434_improve_web_api_async__2.4.6.patch | 690 +++++++++++++++-- ...5_24434_improve_web_api_async__2.4.7.patch | 65 +- 4 files changed, 1970 insertions(+), 170 deletions(-) diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch index a592863..fe8f0d1 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch @@ -1,5 +1,5 @@ diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php -index 6e0aac11d8e98..27c2bf4051ccc 100644 +index 6e0aac11d8e9..27c2bf4051cc 100644 --- a/vendor/magento/module-customer/Model/AccountManagement.php +++ b/vendor/magento/module-customer/Model/AccountManagement.php @@ -876,11 +876,6 @@ public function getConfirmationStatus($customerId) @@ -15,13 +15,13 @@ index 6e0aac11d8e98..27c2bf4051ccc 100644 $this->checkPasswordStrength($password); $customerEmail = $customer->getEmail(); diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php -index 02a05705b57ef..8b4f78ab26c77 100644 +index 02a05705b57e..8b4f78ab26c7 100644 --- a/vendor/magento/module-customer/Model/AccountManagementApi.php +++ b/vendor/magento/module-customer/Model/AccountManagementApi.php @@ -6,16 +6,127 @@ - + namespace Magento\Customer\Model; - + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; @@ -46,7 +46,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 +use Magento\Framework\Stdlib\StringUtils as StringHelper; +use Magento\Store\Model\StoreManagerInterface; +use Psr\Log\LoggerInterface as PsrLogger; - + /** * Account Management service implementation for external API access. + * @@ -153,7 +153,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 + $this->validateCustomerRequest($customer); $customer = parent::createAccount($customer, $password, $redirectUrl); $customer->setConfirmation(null); - + return $customer; } + @@ -179,7 +179,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 } diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php new file mode 100644 -index 0000000000000..295b33d2db14a +index 000000000000..295b33d2db14 --- /dev/null +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php @@ -0,0 +1,89 @@ @@ -272,8 +272,587 @@ index 0000000000000..295b33d2db14a + return null; + } +} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +new file mode 100644 +index 000000000000..074d40021a18 +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +@@ -0,0 +1,421 @@ ++customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); ++ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); ++ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); ++ $this->random = $this->createMock(Random::class); ++ $this->validator = $this->createMock(Validator::class); ++ $this->validationResultsInterfaceFactory = $this->createMock( ++ ValidationResultsInterfaceFactory::class ++ ); ++ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); ++ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); ++ $this->customerRegistry = $this->createMock(CustomerRegistry::class); ++ ++ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); ++ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); ++ $this->share = $this->createMock(Share::class); ++ $this->string = $this->createMock(StringUtils::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->transportBuilder = $this->createMock(TransportBuilder::class); ++ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); ++ $this->registry = $this->createMock(Registry::class); ++ $this->customerViewHelper = $this->createMock(View::class); ++ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); ++ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); ++ $this->objectFactory = $this->createMock(DataObjectFactory::class); ++ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); ++ $this->extensibleDataObjectConverter = $this->createMock( ++ ExtensibleDataObjectConverter::class ++ ); ++ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); ++ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) ++ ->onlyMethods(['addData', 'setData']) ++ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); ++ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); ++ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); ++ ++ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) ++ ->disableOriginalConstructor() ++ ->onlyMethods(['create']) ++ ->getMock(); ++ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->authorizationMock = $this->createMock(Authorization::class); ++ $this->objectManagerHelper = new ObjectManagerHelper($this); ++ $this->accountManagement = $this->objectManagerHelper->getObject( ++ AccountManagementApi::class, ++ [ ++ 'customerFactory' => $this->customerFactory, ++ 'eventManager' => $this->manager, ++ 'storeManager' => $this->storeManager, ++ 'mathRandom' => $this->random, ++ 'validator' => $this->validator, ++ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, ++ 'addressRepository' => $this->addressRepository, ++ 'customerMetadataService' => $this->customerMetadata, ++ 'customerRegistry' => $this->customerRegistry, ++ 'logger' => $this->logger, ++ 'encryptor' => $this->encryptor, ++ 'configShare' => $this->share, ++ 'stringHelper' => $this->string, ++ 'customerRepository' => $this->customerRepository, ++ 'scopeConfig' => $this->scopeConfig, ++ 'transportBuilder' => $this->transportBuilder, ++ 'dataProcessor' => $this->dataObjectProcessor, ++ 'registry' => $this->registry, ++ 'customerViewHelper' => $this->customerViewHelper, ++ 'dateTime' => $this->dateTime, ++ 'customerModel' => $this->customer, ++ 'objectFactory' => $this->objectFactory, ++ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, ++ 'dateTimeFactory' => $this->dateTimeFactory, ++ 'accountConfirmation' => $this->accountConfirmation, ++ 'sessionManager' => $this->sessionManager, ++ 'saveHandler' => $this->saveHandler, ++ 'visitorCollectionFactory' => $this->visitorCollectionFactory, ++ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, ++ 'addressRegistry' => $this->addressRegistryMock, ++ 'allowedCountriesReader' => $this->allowedCountriesReader, ++ 'authorization' => $this->authorizationMock ++ ] ++ ); ++ $this->accountManagementMock = $this->createMock(AccountManagement::class); ++ ++ $this->storeMock = $this->getMockBuilder( ++ StoreInterface::class ++ )->disableOriginalConstructor() ++ ->getMock(); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @throws LocalizedException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforeCreateAccount( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ ++ $customer = $this->getMockBuilder(CustomerInterface::class) ++ ->addMethods(['setData']) ++ ->getMockForAbstractClass(); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $customer->method('getWebsiteId')->willReturn(2); ++ $customer->method('getStoreId')->willReturn(1); ++ $customer->method('setData')->willReturn(1); ++ ++ $this->customerRepository->method('get')->willReturn($customer); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $this->customerRepository->method('save')->willReturn($customer); ++ ++ if (!$willThrowException) { ++ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); ++ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); ++ $this->random->method('getUniqueHash')->willReturn('testabc'); ++ $date = $this->getMockBuilder(\DateTime::class) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory->expects(static::once()) ++ ->method('create') ++ ->willReturn($date); ++ $date->expects(static::once()) ++ ->method('format') ++ ->with('Y-m-d H:i:s') ++ ->willReturn('2015-01-01 00:00:00'); ++ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); ++ $this->storeManager->method('getStores') ++ ->willReturn([$this->storeMock]); ++ } ++ $this->accountManagement->createAccount($customer); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +index 8ff6a8585212..cbe0a18e4b17 100644 +--- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void + $minPasswordLength = 5; + $minCharacterSetsNum = 2; + $defaultGroupId = 1; +- $requestedGroupId = 3; + + $datetime = $this->prepareDateTimeFactory(); + +@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void + return null; + } + })); +- $customer->expects($this->atLeastOnce()) +- ->method('getGroupId') +- ->willReturn($requestedGroupId); + $customer + ->method('setGroupId') + ->willReturnOnConsecutiveCalls(null, $defaultGroupId); +diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +new file mode 100644 +index 000000000000..107df2c2863e +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +@@ -0,0 +1,112 @@ ++authorizationMock = $this->createMock(Authorization::class); ++ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ ++ 'authorization' => $this->authorizationMock ++ ]); ++ $this->massScheduleMock = $this->createMock(MassSchedule::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforePublishMass( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $customer = $this->getMockForAbstractClass(CustomerInterface::class); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $entitiesArray = [ ++ [$customer, 'Password1', ''] ++ ]; ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ $this->plugin->beforePublishMass( ++ $this->massScheduleMock, ++ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', ++ $entitiesArray, ++ '', ++ '' ++ ); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json +index 2d76da56bff7..ff34d423c2da 100644 +--- a/vendor/magento/module-customer/composer.json ++++ b/vendor/magento/module-customer/composer.json +@@ -29,5 +29,6 @@ + "suggest": { + "magento/module-cookie": "100.4.*", + "magento/module-customer-sample-data": "Sample Data version: 100.4.*", +- "magento/module-webapi": "100.4.*" ++ "magento/module-webapi": "100.4.*", ++ "magento/module-asynchronous-operations": "100.4.*" + }, diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml -index 156986b7b4a3c..120a8dda8aece 100644 +index 156986b7b4a3..120a8dda8aec 100644 --- a/vendor/magento/module-customer/etc/di.xml +++ b/vendor/magento/module-customer/etc/di.xml @@ -560,4 +560,9 @@ @@ -287,7 +866,7 @@ index 156986b7b4a3c..120a8dda8aece 100644 + diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea198..a7cce5b03a26d 100644 +index 79d98968ea19..a7cce5b03a26 100644 --- a/vendor/magento/module-quote/etc/webapi.xml +++ b/vendor/magento/module-quote/etc/webapi.xml @@ -98,6 +98,9 @@ @@ -301,13 +880,13 @@ index 79d98968ea198..a7cce5b03a26d 100644 diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 8601e5011bda7..2f46685c6b117 100644 +index 8601e5011bda..93555559ac9a 100644 --- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,10 +8,12 @@ - + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - + +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\AuthorizationException; @@ -318,7 +897,7 @@ index 8601e5011bda7..2f46685c6b117 100644 use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; @@ -24,6 +26,8 @@ - + /** * This class is responsible for retrieving resolved input data + * @@ -326,24 +905,19 @@ index 8601e5011bda7..2f46685c6b117 100644 */ class InputParamsResolver { -@@ -61,6 +65,16 @@ class InputParamsResolver +@@ -61,6 +65,11 @@ class InputParamsResolver */ private $inputArraySizeLimitValue; - + + /** + * @var MethodsMap + */ + private $methodsMap; -+ -+ /** -+ * @var array -+ */ -+ private array $inputData = []; + /** * Initialize dependencies. * -@@ -72,6 +86,7 @@ class InputParamsResolver +@@ -72,6 +81,7 @@ class InputParamsResolver * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue @@ -351,7 +925,7 @@ index 8601e5011bda7..2f46685c6b117 100644 */ public function __construct( RestRequest $request, -@@ -81,7 +96,8 @@ public function __construct( +@@ -81,7 +91,8 @@ public function __construct( RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, bool $isBulk = false, @@ -361,17 +935,17 @@ index 8601e5011bda7..2f46685c6b117 100644 ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +108,8 @@ public function __construct( +@@ -92,6 +103,8 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); + $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() + ->get(MethodsMap::class); } - + /** -@@ -113,12 +131,19 @@ public function resolve() - +@@ -113,12 +126,19 @@ public function resolve() + $this->requestValidator->validate(); $webapiResolvedParams = []; + $inputData = $this->getInputData(); @@ -379,7 +953,7 @@ index 8601e5011bda7..2f46685c6b117 100644 $routeServiceClass = $route->getServiceClass(); $routeServiceMethod = $route->getServiceMethod(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - + - foreach ($this->getInputData() as $key => $singleEntityParams) { + $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); + @@ -391,22 +965,9 @@ index 8601e5011bda7..2f46685c6b117 100644 $webapiResolvedParams[$key] = $this->resolveBulkItemParams( $singleEntityParams, $routeServiceClass, -@@ -136,17 +161,36 @@ public function resolve() - */ - public function getInputData() - { -+ if (!empty($this->inputData)) { -+ return $this->inputData; -+ } -+ - if ($this->isBulk === false) { -- return [$this->inputParamsResolver->getInputData()]; -+ $this->inputData = [$this->inputParamsResolver->getInputData()]; -+ -+ return $this->inputData; - } +@@ -142,11 +162,22 @@ public function getInputData() $inputData = $this->request->getRequestData(); - + $httpMethod = $this->request->getHttpMethod(); - if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { + if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { @@ -415,7 +976,7 @@ index 8601e5011bda7..2f46685c6b117 100644 } - return $inputData; + -+ $this->inputData = array_map(function ($singleEntityParams) { ++ return array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( @@ -426,12 +987,10 @@ index 8601e5011bda7..2f46685c6b117 100644 + + return $singleEntityParams; + }, $inputData); -+ -+ return $this->inputData; } - + /** -@@ -179,4 +223,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s +@@ -179,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s { return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } @@ -496,3 +1055,42 @@ index 8601e5011bda7..2f46685c6b117 100644 + } + } } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index ce9e4ee94178..e08fe0388cfb 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -10,10 +10,13 @@ + + class GuestCartManagementTest extends WebapiAbstract + { +- const SERVICE_VERSION = 'V1'; +- const SERVICE_NAME = 'quoteGuestCartManagementV1'; +- const RESOURCE_PATH = '/V1/guest-carts/'; ++ public const SERVICE_VERSION = 'V1'; ++ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; ++ public const RESOURCE_PATH = '/V1/guest-carts/'; + ++ /** ++ * @var array List of created quotes ++ */ + protected $createdQuotes = []; + + /** +@@ -339,7 +342,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index efc7e669b360..18ffe842c794 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -109,3 +109,4 @@ app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/ + app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php + app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php + app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch index 30f8eab..53e4e8e 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch @@ -1,5 +1,5 @@ diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php -index d70058aef445f..513c0b0717e85 100644 +index d70058aef445..513c0b0717e8 100644 --- a/vendor/magento/module-customer/Model/AccountManagement.php +++ b/vendor/magento/module-customer/Model/AccountManagement.php @@ -879,11 +879,6 @@ public function getConfirmationStatus($customerId) @@ -15,13 +15,13 @@ index d70058aef445f..513c0b0717e85 100644 $this->checkPasswordStrength($password); $customerEmail = $customer->getEmail(); diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php -index 02a05705b57ef..8b4f78ab26c77 100644 +index 02a05705b57e..8b4f78ab26c7 100644 --- a/vendor/magento/module-customer/Model/AccountManagementApi.php +++ b/vendor/magento/module-customer/Model/AccountManagementApi.php @@ -6,16 +6,127 @@ - + namespace Magento\Customer\Model; - + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; @@ -46,7 +46,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 +use Magento\Framework\Stdlib\StringUtils as StringHelper; +use Magento\Store\Model\StoreManagerInterface; +use Psr\Log\LoggerInterface as PsrLogger; - + /** * Account Management service implementation for external API access. + * @@ -153,7 +153,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 + $this->validateCustomerRequest($customer); $customer = parent::createAccount($customer, $password, $redirectUrl); $customer->setConfirmation(null); - + return $customer; } + @@ -179,7 +179,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 } diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php new file mode 100644 -index 0000000000000..295b33d2db14a +index 000000000000..295b33d2db14 --- /dev/null +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php @@ -0,0 +1,89 @@ @@ -272,8 +272,587 @@ index 0000000000000..295b33d2db14a + return null; + } +} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +new file mode 100644 +index 000000000000..074d40021a18 +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +@@ -0,0 +1,421 @@ ++customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); ++ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); ++ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); ++ $this->random = $this->createMock(Random::class); ++ $this->validator = $this->createMock(Validator::class); ++ $this->validationResultsInterfaceFactory = $this->createMock( ++ ValidationResultsInterfaceFactory::class ++ ); ++ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); ++ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); ++ $this->customerRegistry = $this->createMock(CustomerRegistry::class); ++ ++ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); ++ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); ++ $this->share = $this->createMock(Share::class); ++ $this->string = $this->createMock(StringUtils::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->transportBuilder = $this->createMock(TransportBuilder::class); ++ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); ++ $this->registry = $this->createMock(Registry::class); ++ $this->customerViewHelper = $this->createMock(View::class); ++ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); ++ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); ++ $this->objectFactory = $this->createMock(DataObjectFactory::class); ++ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); ++ $this->extensibleDataObjectConverter = $this->createMock( ++ ExtensibleDataObjectConverter::class ++ ); ++ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); ++ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) ++ ->onlyMethods(['addData', 'setData']) ++ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); ++ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); ++ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); ++ ++ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) ++ ->disableOriginalConstructor() ++ ->onlyMethods(['create']) ++ ->getMock(); ++ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->authorizationMock = $this->createMock(Authorization::class); ++ $this->objectManagerHelper = new ObjectManagerHelper($this); ++ $this->accountManagement = $this->objectManagerHelper->getObject( ++ AccountManagementApi::class, ++ [ ++ 'customerFactory' => $this->customerFactory, ++ 'eventManager' => $this->manager, ++ 'storeManager' => $this->storeManager, ++ 'mathRandom' => $this->random, ++ 'validator' => $this->validator, ++ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, ++ 'addressRepository' => $this->addressRepository, ++ 'customerMetadataService' => $this->customerMetadata, ++ 'customerRegistry' => $this->customerRegistry, ++ 'logger' => $this->logger, ++ 'encryptor' => $this->encryptor, ++ 'configShare' => $this->share, ++ 'stringHelper' => $this->string, ++ 'customerRepository' => $this->customerRepository, ++ 'scopeConfig' => $this->scopeConfig, ++ 'transportBuilder' => $this->transportBuilder, ++ 'dataProcessor' => $this->dataObjectProcessor, ++ 'registry' => $this->registry, ++ 'customerViewHelper' => $this->customerViewHelper, ++ 'dateTime' => $this->dateTime, ++ 'customerModel' => $this->customer, ++ 'objectFactory' => $this->objectFactory, ++ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, ++ 'dateTimeFactory' => $this->dateTimeFactory, ++ 'accountConfirmation' => $this->accountConfirmation, ++ 'sessionManager' => $this->sessionManager, ++ 'saveHandler' => $this->saveHandler, ++ 'visitorCollectionFactory' => $this->visitorCollectionFactory, ++ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, ++ 'addressRegistry' => $this->addressRegistryMock, ++ 'allowedCountriesReader' => $this->allowedCountriesReader, ++ 'authorization' => $this->authorizationMock ++ ] ++ ); ++ $this->accountManagementMock = $this->createMock(AccountManagement::class); ++ ++ $this->storeMock = $this->getMockBuilder( ++ StoreInterface::class ++ )->disableOriginalConstructor() ++ ->getMock(); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @throws LocalizedException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforeCreateAccount( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ ++ $customer = $this->getMockBuilder(CustomerInterface::class) ++ ->addMethods(['setData']) ++ ->getMockForAbstractClass(); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $customer->method('getWebsiteId')->willReturn(2); ++ $customer->method('getStoreId')->willReturn(1); ++ $customer->method('setData')->willReturn(1); ++ ++ $this->customerRepository->method('get')->willReturn($customer); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $this->customerRepository->method('save')->willReturn($customer); ++ ++ if (!$willThrowException) { ++ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); ++ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); ++ $this->random->method('getUniqueHash')->willReturn('testabc'); ++ $date = $this->getMockBuilder(\DateTime::class) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory->expects(static::once()) ++ ->method('create') ++ ->willReturn($date); ++ $date->expects(static::once()) ++ ->method('format') ++ ->with('Y-m-d H:i:s') ++ ->willReturn('2015-01-01 00:00:00'); ++ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); ++ $this->storeManager->method('getStores') ++ ->willReturn([$this->storeMock]); ++ } ++ $this->accountManagement->createAccount($customer); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +index 8ff6a8585212..cbe0a18e4b17 100644 +--- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void + $minPasswordLength = 5; + $minCharacterSetsNum = 2; + $defaultGroupId = 1; +- $requestedGroupId = 3; + + $datetime = $this->prepareDateTimeFactory(); + +@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void + return null; + } + })); +- $customer->expects($this->atLeastOnce()) +- ->method('getGroupId') +- ->willReturn($requestedGroupId); + $customer + ->method('setGroupId') + ->willReturnOnConsecutiveCalls(null, $defaultGroupId); +diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +new file mode 100644 +index 000000000000..107df2c2863e +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +@@ -0,0 +1,112 @@ ++authorizationMock = $this->createMock(Authorization::class); ++ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ ++ 'authorization' => $this->authorizationMock ++ ]); ++ $this->massScheduleMock = $this->createMock(MassSchedule::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforePublishMass( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $customer = $this->getMockForAbstractClass(CustomerInterface::class); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $entitiesArray = [ ++ [$customer, 'Password1', ''] ++ ]; ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ $this->plugin->beforePublishMass( ++ $this->massScheduleMock, ++ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', ++ $entitiesArray, ++ '', ++ '' ++ ); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json +index 2d76da56bff7..ff34d423c2da 100644 +--- a/vendor/magento/module-customer/composer.json ++++ b/vendor/magento/module-customer/composer.json +@@ -35,5 +35,6 @@ + "suggest": { + "magento/module-cookie": "100.4.*", + "magento/module-customer-sample-data": "Sample Data version: 100.4.*", +- "magento/module-webapi": "100.4.*" ++ "magento/module-webapi": "100.4.*", ++ "magento/module-asynchronous-operations": "100.4.*" + }, diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml -index 31b79935ad9ab..4cda16e121c97 100644 +index 31b79935ad9a..4cda16e121c9 100644 --- a/vendor/magento/module-customer/etc/di.xml +++ b/vendor/magento/module-customer/etc/di.xml @@ -567,4 +567,9 @@ @@ -287,7 +866,7 @@ index 31b79935ad9ab..4cda16e121c97 100644 + diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea198..a7cce5b03a26d 100644 +index 79d98968ea19..a7cce5b03a26 100644 --- a/vendor/magento/module-quote/etc/webapi.xml +++ b/vendor/magento/module-quote/etc/webapi.xml @@ -98,6 +98,9 @@ @@ -301,13 +880,13 @@ index 79d98968ea198..a7cce5b03a26d 100644 diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc5..2f46685c6b117 100644 +index 6718087888bc..93555559ac9a 100644 --- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,10 +8,12 @@ - + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - + +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\AuthorizationException; @@ -318,7 +897,7 @@ index 6718087888bc5..2f46685c6b117 100644 use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; @@ -24,6 +26,8 @@ - + /** * This class is responsible for retrieving resolved input data + * @@ -326,24 +905,19 @@ index 6718087888bc5..2f46685c6b117 100644 */ class InputParamsResolver { -@@ -61,6 +65,16 @@ class InputParamsResolver +@@ -61,6 +65,11 @@ class InputParamsResolver */ private $inputArraySizeLimitValue; - + + /** + * @var MethodsMap + */ + private $methodsMap; -+ -+ /** -+ * @var array -+ */ -+ private array $inputData = []; + /** * Initialize dependencies. * -@@ -72,6 +86,7 @@ class InputParamsResolver +@@ -72,6 +81,7 @@ class InputParamsResolver * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue @@ -351,7 +925,7 @@ index 6718087888bc5..2f46685c6b117 100644 */ public function __construct( RestRequest $request, -@@ -81,7 +96,8 @@ public function __construct( +@@ -81,7 +91,8 @@ public function __construct( RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, bool $isBulk = false, @@ -361,19 +935,19 @@ index 6718087888bc5..2f46685c6b117 100644 ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +108,8 @@ public function __construct( +@@ -92,6 +103,8 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); + $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() + ->get(MethodsMap::class); } - + /** -@@ -119,7 +137,13 @@ public function resolve() +@@ -119,7 +132,13 @@ public function resolve() $routeServiceMethod = $route->getServiceMethod(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - + + $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); + foreach ($inputData as $key => $singleEntityParams) { @@ -384,22 +958,9 @@ index 6718087888bc5..2f46685c6b117 100644 $webapiResolvedParams[$key] = $this->resolveBulkItemParams( $singleEntityParams, $routeServiceClass, -@@ -137,17 +161,36 @@ public function resolve() - */ - public function getInputData() - { -+ if (!empty($this->inputData)) { -+ return $this->inputData; -+ } -+ - if ($this->isBulk === false) { -- return [$this->inputParamsResolver->getInputData()]; -+ $this->inputData = [$this->inputParamsResolver->getInputData()]; -+ -+ return $this->inputData; - } +@@ -143,11 +162,22 @@ public function getInputData() $inputData = $this->request->getRequestData(); - + $httpMethod = $this->request->getHttpMethod(); - if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { + if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { @@ -408,7 +969,7 @@ index 6718087888bc5..2f46685c6b117 100644 } - return $inputData; + -+ $this->inputData = array_map(function ($singleEntityParams) { ++ return array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( @@ -419,12 +980,10 @@ index 6718087888bc5..2f46685c6b117 100644 + + return $singleEntityParams; + }, $inputData); -+ -+ return $this->inputData; } - + /** -@@ -180,4 +223,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s +@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s { return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } @@ -489,3 +1048,45 @@ index 6718087888bc5..2f46685c6b117 100644 + } + } } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index ce9e4ee94178..e08fe0388cfb 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -10,10 +10,13 @@ + + class GuestCartManagementTest extends WebapiAbstract + { +- const SERVICE_VERSION = 'V1'; +- const SERVICE_NAME = 'quoteGuestCartManagementV1'; +- const RESOURCE_PATH = '/V1/guest-carts/'; ++ public const SERVICE_VERSION = 'V1'; ++ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; ++ public const RESOURCE_PATH = '/V1/guest-carts/'; + ++ /** ++ * @var array List of created quotes ++ */ + protected $createdQuotes = []; + + /** +@@ -339,7 +342,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index 9991dd4e05fe..dc98670bd000 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -110,4 +110,5 @@ app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php + app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php + app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml + app/code/Magento/GoogleGtag +-app/code/Magento/AdminAdobeIms/Observer/AuthObserver +\ No newline at end of file ++app/code/Magento/AdminAdobeIms/Observer/AuthObserver ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch index b1dcf38..d18b1e8 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch @@ -1,5 +1,5 @@ diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php -index 702b6aeb68653..bfebafeb0330b 100644 +index 702b6aeb6865..bfebafeb0330 100644 --- a/vendor/magento/module-customer/Model/AccountManagement.php +++ b/vendor/magento/module-customer/Model/AccountManagement.php @@ -882,11 +882,6 @@ public function getConfirmationStatus($customerId) @@ -15,13 +15,13 @@ index 702b6aeb68653..bfebafeb0330b 100644 $this->checkPasswordStrength($password); $customerEmail = $customer->getEmail(); diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php -index 02a05705b57ef..8b4f78ab26c77 100644 +index 02a05705b57e..8b4f78ab26c7 100644 --- a/vendor/magento/module-customer/Model/AccountManagementApi.php +++ b/vendor/magento/module-customer/Model/AccountManagementApi.php @@ -6,16 +6,127 @@ - + namespace Magento\Customer\Model; - + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; @@ -46,7 +46,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 +use Magento\Framework\Stdlib\StringUtils as StringHelper; +use Magento\Store\Model\StoreManagerInterface; +use Psr\Log\LoggerInterface as PsrLogger; - + /** * Account Management service implementation for external API access. + * @@ -153,7 +153,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 + $this->validateCustomerRequest($customer); $customer = parent::createAccount($customer, $password, $redirectUrl); $customer->setConfirmation(null); - + return $customer; } + @@ -179,7 +179,7 @@ index 02a05705b57ef..8b4f78ab26c77 100644 } diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php new file mode 100644 -index 0000000000000..cdda3016694ca +index 000000000000..cdda3016694c --- /dev/null +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php @@ -0,0 +1,90 @@ @@ -273,8 +273,587 @@ index 0000000000000..cdda3016694ca + return null; + } +} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +new file mode 100644 +index 000000000000..074d40021a18 +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php +@@ -0,0 +1,421 @@ ++customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); ++ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); ++ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); ++ $this->random = $this->createMock(Random::class); ++ $this->validator = $this->createMock(Validator::class); ++ $this->validationResultsInterfaceFactory = $this->createMock( ++ ValidationResultsInterfaceFactory::class ++ ); ++ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); ++ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); ++ $this->customerRegistry = $this->createMock(CustomerRegistry::class); ++ ++ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); ++ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); ++ $this->share = $this->createMock(Share::class); ++ $this->string = $this->createMock(StringUtils::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->transportBuilder = $this->createMock(TransportBuilder::class); ++ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); ++ $this->registry = $this->createMock(Registry::class); ++ $this->customerViewHelper = $this->createMock(View::class); ++ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); ++ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); ++ $this->objectFactory = $this->createMock(DataObjectFactory::class); ++ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); ++ $this->extensibleDataObjectConverter = $this->createMock( ++ ExtensibleDataObjectConverter::class ++ ); ++ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); ++ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) ++ ->onlyMethods(['addData', 'setData']) ++ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); ++ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); ++ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); ++ ++ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) ++ ->disableOriginalConstructor() ++ ->onlyMethods(['create']) ++ ->getMock(); ++ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) ++ ->disableOriginalConstructor() ++ ->getMockForAbstractClass(); ++ $this->authorizationMock = $this->createMock(Authorization::class); ++ $this->objectManagerHelper = new ObjectManagerHelper($this); ++ $this->accountManagement = $this->objectManagerHelper->getObject( ++ AccountManagementApi::class, ++ [ ++ 'customerFactory' => $this->customerFactory, ++ 'eventManager' => $this->manager, ++ 'storeManager' => $this->storeManager, ++ 'mathRandom' => $this->random, ++ 'validator' => $this->validator, ++ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, ++ 'addressRepository' => $this->addressRepository, ++ 'customerMetadataService' => $this->customerMetadata, ++ 'customerRegistry' => $this->customerRegistry, ++ 'logger' => $this->logger, ++ 'encryptor' => $this->encryptor, ++ 'configShare' => $this->share, ++ 'stringHelper' => $this->string, ++ 'customerRepository' => $this->customerRepository, ++ 'scopeConfig' => $this->scopeConfig, ++ 'transportBuilder' => $this->transportBuilder, ++ 'dataProcessor' => $this->dataObjectProcessor, ++ 'registry' => $this->registry, ++ 'customerViewHelper' => $this->customerViewHelper, ++ 'dateTime' => $this->dateTime, ++ 'customerModel' => $this->customer, ++ 'objectFactory' => $this->objectFactory, ++ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, ++ 'dateTimeFactory' => $this->dateTimeFactory, ++ 'accountConfirmation' => $this->accountConfirmation, ++ 'sessionManager' => $this->sessionManager, ++ 'saveHandler' => $this->saveHandler, ++ 'visitorCollectionFactory' => $this->visitorCollectionFactory, ++ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, ++ 'addressRegistry' => $this->addressRegistryMock, ++ 'allowedCountriesReader' => $this->allowedCountriesReader, ++ 'authorization' => $this->authorizationMock ++ ] ++ ); ++ $this->accountManagementMock = $this->createMock(AccountManagement::class); ++ ++ $this->storeMock = $this->getMockBuilder( ++ StoreInterface::class ++ )->disableOriginalConstructor() ++ ->getMock(); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @throws LocalizedException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforeCreateAccount( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ ++ $customer = $this->getMockBuilder(CustomerInterface::class) ++ ->addMethods(['setData']) ++ ->getMockForAbstractClass(); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $customer->method('getWebsiteId')->willReturn(2); ++ $customer->method('getStoreId')->willReturn(1); ++ $customer->method('setData')->willReturn(1); ++ ++ $this->customerRepository->method('get')->willReturn($customer); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $this->customerRepository->method('save')->willReturn($customer); ++ ++ if (!$willThrowException) { ++ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); ++ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); ++ $this->random->method('getUniqueHash')->willReturn('testabc'); ++ $date = $this->getMockBuilder(\DateTime::class) ++ ->disableOriginalConstructor() ++ ->getMock(); ++ $this->dateTimeFactory->expects(static::once()) ++ ->method('create') ++ ->willReturn($date); ++ $date->expects(static::once()) ++ ->method('format') ++ ->with('Y-m-d H:i:s') ++ ->willReturn('2015-01-01 00:00:00'); ++ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); ++ $this->storeManager->method('getStores') ++ ->willReturn([$this->storeMock]); ++ } ++ $this->accountManagement->createAccount($customer); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +index 9e68d53fd594..e2b507f6fe37 100644 +--- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php ++++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php +@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void + $minPasswordLength = 5; + $minCharacterSetsNum = 2; + $defaultGroupId = 1; +- $requestedGroupId = 3; + + $datetime = $this->prepareDateTimeFactory(); + +@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void + return null; + } + })); +- $customer->expects($this->atLeastOnce()) +- ->method('getGroupId') +- ->willReturn($requestedGroupId); + $customer + ->method('setGroupId') + ->willReturnOnConsecutiveCalls(null, $defaultGroupId); +diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +new file mode 100644 +index 000000000000..107df2c2863e +--- /dev/null ++++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php +@@ -0,0 +1,112 @@ ++authorizationMock = $this->createMock(Authorization::class); ++ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ ++ 'authorization' => $this->authorizationMock ++ ]); ++ $this->massScheduleMock = $this->createMock(MassSchedule::class); ++ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); ++ } ++ ++ /** ++ * Verify that only authorized request will be able to change groupId ++ * ++ * @param int $groupId ++ * @param int $customerId ++ * @param bool $isAllowed ++ * @param int $willThrowException ++ * @return void ++ * @throws AuthorizationException ++ * @dataProvider customerDataProvider ++ */ ++ public function testBeforePublishMass( ++ int $groupId, ++ int $customerId, ++ bool $isAllowed, ++ int $willThrowException ++ ): void { ++ if ($willThrowException) { ++ $this->expectException(AuthorizationException::class); ++ } else { ++ $this->expectNotToPerformAssertions(); ++ } ++ $customer = $this->getMockForAbstractClass(CustomerInterface::class); ++ $customer->method('getGroupId')->willReturn($groupId); ++ $customer->method('getId')->willReturn($customerId); ++ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); ++ $entitiesArray = [ ++ [$customer, 'Password1', ''] ++ ]; ++ $this->authorizationMock ++ ->expects($this->once()) ++ ->method('isAllowed') ++ ->with('Magento_Customer::manage') ++ ->willReturn($isAllowed); ++ $this->plugin->beforePublishMass( ++ $this->massScheduleMock, ++ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', ++ $entitiesArray, ++ '', ++ '' ++ ); ++ } ++ ++ /** ++ * @return array ++ */ ++ public function customerDataProvider(): array ++ { ++ return [ ++ [3, 1, false, 1], ++ [3, 1, true, 0] ++ ]; ++ } ++} +diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json +index ef2047644759..39c82c20f2ec 100644 +--- a/vendor/magento/module-customer/composer.json ++++ b/vendor/magento/module-customer/composer.json +@@ -29,5 +29,6 @@ + "suggest": { + "magento/module-cookie": "100.4.*", + "magento/module-customer-sample-data": "Sample Data version: 100.4.*", +- "magento/module-webapi": "100.4.*" ++ "magento/module-webapi": "100.4.*", ++ "magento/module-asynchronous-operations": "100.4.*" + }, diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml -index b178f51f89199..96fd4b86be702 100644 +index b178f51f8919..96fd4b86be70 100644 --- a/vendor/magento/module-customer/etc/di.xml +++ b/vendor/magento/module-customer/etc/di.xml @@ -585,4 +585,9 @@ @@ -288,7 +867,7 @@ index b178f51f89199..96fd4b86be702 100644 + diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea198..a7cce5b03a26d 100644 +index 79d98968ea19..a7cce5b03a26 100644 --- a/vendor/magento/module-quote/etc/webapi.xml +++ b/vendor/magento/module-quote/etc/webapi.xml @@ -98,6 +98,9 @@ @@ -302,13 +881,13 @@ index 79d98968ea198..a7cce5b03a26d 100644 diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc5..2f46685c6b117 100644 +index 6718087888bc..93555559ac9a 100644 --- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,10 +8,12 @@ - + namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - + +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\AuthorizationException; @@ -319,7 +898,7 @@ index 6718087888bc5..2f46685c6b117 100644 use Magento\Framework\Webapi\Rest\Request as RestRequest; use Magento\Framework\Webapi\ServiceInputProcessor; @@ -24,6 +26,8 @@ - + /** * This class is responsible for retrieving resolved input data + * @@ -327,24 +906,19 @@ index 6718087888bc5..2f46685c6b117 100644 */ class InputParamsResolver { -@@ -61,6 +65,16 @@ class InputParamsResolver +@@ -61,6 +65,11 @@ class InputParamsResolver */ private $inputArraySizeLimitValue; - + + /** + * @var MethodsMap + */ + private $methodsMap; -+ -+ /** -+ * @var array -+ */ -+ private array $inputData = []; + /** * Initialize dependencies. * -@@ -72,6 +86,7 @@ class InputParamsResolver +@@ -72,6 +81,7 @@ class InputParamsResolver * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue @@ -352,7 +926,7 @@ index 6718087888bc5..2f46685c6b117 100644 */ public function __construct( RestRequest $request, -@@ -81,7 +96,8 @@ public function __construct( +@@ -81,7 +91,8 @@ public function __construct( RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, bool $isBulk = false, @@ -362,19 +936,19 @@ index 6718087888bc5..2f46685c6b117 100644 ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +108,8 @@ public function __construct( +@@ -92,6 +103,8 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); + $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() + ->get(MethodsMap::class); } - + /** -@@ -119,7 +137,13 @@ public function resolve() +@@ -119,7 +132,13 @@ public function resolve() $routeServiceMethod = $route->getServiceMethod(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - + + $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); + foreach ($inputData as $key => $singleEntityParams) { @@ -385,22 +959,9 @@ index 6718087888bc5..2f46685c6b117 100644 $webapiResolvedParams[$key] = $this->resolveBulkItemParams( $singleEntityParams, $routeServiceClass, -@@ -137,17 +161,36 @@ public function resolve() - */ - public function getInputData() - { -+ if (!empty($this->inputData)) { -+ return $this->inputData; -+ } -+ - if ($this->isBulk === false) { -- return [$this->inputParamsResolver->getInputData()]; -+ $this->inputData = [$this->inputParamsResolver->getInputData()]; -+ -+ return $this->inputData; - } +@@ -143,11 +162,22 @@ public function getInputData() $inputData = $this->request->getRequestData(); - + $httpMethod = $this->request->getHttpMethod(); - if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { + if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { @@ -409,7 +970,7 @@ index 6718087888bc5..2f46685c6b117 100644 } - return $inputData; + -+ $this->inputData = array_map(function ($singleEntityParams) { ++ return array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( @@ -420,12 +981,10 @@ index 6718087888bc5..2f46685c6b117 100644 + + return $singleEntityParams; + }, $inputData); -+ -+ return $this->inputData; } - + /** -@@ -180,4 +223,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s +@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s { return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } @@ -490,3 +1049,42 @@ index 6718087888bc5..2f46685c6b117 100644 + } + } } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index ce9e4ee94178..e08fe0388cfb 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -10,10 +10,13 @@ + + class GuestCartManagementTest extends WebapiAbstract + { +- const SERVICE_VERSION = 'V1'; +- const SERVICE_NAME = 'quoteGuestCartManagementV1'; +- const RESOURCE_PATH = '/V1/guest-carts/'; ++ public const SERVICE_VERSION = 'V1'; ++ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; ++ public const RESOURCE_PATH = '/V1/guest-carts/'; + ++ /** ++ * @var array List of created quotes ++ */ + protected $createdQuotes = []; + + /** +@@ -339,7 +342,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index 80fe4ec247a6..50285d7492e8 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed + app/code/Magento/GoogleGtag + app/code/Magento/AdminAdobeIms/Observer/AuthObserver + app/code/Magento/OpenSearch/SearchAdapter/Adapter ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch index 507e252..038b6c8 100644 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch +++ b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch @@ -1,5 +1,5 @@ diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 5b5c8ce1fc0ca..295b33d2db14a 100644 +index 5b5c8ce1fc0c..295b33d2db14 100644 --- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php +++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php @@ -9,7 +9,6 @@ @@ -37,7 +37,7 @@ index 5b5c8ce1fc0ca..295b33d2db14a 100644 foreach ($entityParams as $entity) { if ($entity instanceof CustomerInterface) { diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea198..a7cce5b03a26d 100644 +index 79d98968ea19..a7cce5b03a26 100644 --- a/vendor/magento/module-quote/etc/webapi.xml +++ b/vendor/magento/module-quote/etc/webapi.xml @@ -98,6 +98,9 @@ @@ -51,7 +51,7 @@ index 79d98968ea198..a7cce5b03a26d 100644 diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc5..7be0b259c251e 100644 +index 6718087888bc..6e159eaddf16 100644 --- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,10 +8,12 @@ @@ -76,7 +76,7 @@ index 6718087888bc5..7be0b259c251e 100644 */ class InputParamsResolver { -@@ -61,6 +65,16 @@ class InputParamsResolver +@@ -61,6 +65,11 @@ class InputParamsResolver */ private $inputArraySizeLimitValue; @@ -84,16 +84,11 @@ index 6718087888bc5..7be0b259c251e 100644 + * @var MethodsMap + */ + private $methodsMap; -+ -+ /** -+ * @var array -+ */ -+ private array $inputData = []; + /** * Initialize dependencies. * -@@ -72,6 +86,7 @@ class InputParamsResolver +@@ -72,6 +81,7 @@ class InputParamsResolver * @param WebapiInputParamsResolver $inputParamsResolver * @param bool $isBulk * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue @@ -101,7 +96,7 @@ index 6718087888bc5..7be0b259c251e 100644 */ public function __construct( RestRequest $request, -@@ -81,7 +96,8 @@ public function __construct( +@@ -81,7 +91,8 @@ public function __construct( RequestValidator $requestValidator, WebapiInputParamsResolver $inputParamsResolver, bool $isBulk = false, @@ -111,7 +106,7 @@ index 6718087888bc5..7be0b259c251e 100644 ) { $this->request = $request; $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +108,8 @@ public function __construct( +@@ -92,6 +103,8 @@ public function __construct( $this->isBulk = $isBulk; $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() ->get(InputArraySizeLimitValue::class); @@ -120,7 +115,7 @@ index 6718087888bc5..7be0b259c251e 100644 } /** -@@ -119,7 +137,13 @@ public function resolve() +@@ -119,7 +132,13 @@ public function resolve() $routeServiceMethod = $route->getServiceMethod(); $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); @@ -134,20 +129,7 @@ index 6718087888bc5..7be0b259c251e 100644 $webapiResolvedParams[$key] = $this->resolveBulkItemParams( $singleEntityParams, $routeServiceClass, -@@ -137,17 +161,36 @@ public function resolve() - */ - public function getInputData() - { -+ if (!empty($this->inputData)) { -+ return $this->inputData; -+ } -+ - if ($this->isBulk === false) { -- return [$this->inputParamsResolver->getInputData()]; -+ $this->inputData = [$this->inputParamsResolver->getInputData()]; -+ -+ return $this->inputData; - } +@@ -143,11 +162,22 @@ public function getInputData() $inputData = $this->request->getRequestData(); $httpMethod = $this->request->getHttpMethod(); @@ -158,7 +140,7 @@ index 6718087888bc5..7be0b259c251e 100644 } - return $inputData; + -+ $this->inputData = array_map(function ($singleEntityParams) { ++ return array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( @@ -169,12 +151,10 @@ index 6718087888bc5..7be0b259c251e 100644 + + return $singleEntityParams; + }, $inputData); -+ -+ return $this->inputData; } /** -@@ -180,4 +223,65 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s +@@ -180,4 +210,65 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s { return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); } @@ -240,3 +220,26 @@ index 6718087888bc5..7be0b259c251e 100644 + } + } } +diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +index 68cc2c2b2315..6f08b21f3812 100644 +--- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php ++++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +@@ -354,7 +354,7 @@ public function testPlaceOrder() + public function testAssignCustomerByGuestUser() + { + $this->expectException(\Exception::class); +- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); ++ $this->expectExceptionMessage('Enter and try again.'); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); +diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +index 08ba4bba28c6..c9d07aa2abed 100644 +--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt ++++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed + app/code/Magento/GoogleGtag + app/code/Magento/AdminAdobeIms/Observer/AuthObserver + app/code/Magento/OpenSearch/SearchAdapter/Adapter ++app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php + From 26311c7c47a279bfccddd61790e88ebd924ccbdc Mon Sep 17 00:00:00 2001 From: Oleksandr Gorbivskyi Date: Mon, 14 Apr 2025 09:44:27 -0700 Subject: [PATCH 69/82] MCLOUD-13605: Add ACSD-65540(ACP2E-3833) to the cloud patches --- patches.json | 3 +++ ...or_due_to_the_REGEXP_LIKE_function__1.5.2.patch | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 patches/MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch diff --git a/patches.json b/patches.json index b59583d..67069ab 100644 --- a/patches.json +++ b/patches.json @@ -394,6 +394,9 @@ }, "Fixes the issue where the file generated after Requisition List export is not removed from the var/ directory": { ">=1.3.1 <1.3.6": "MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch" + }, + "Fixes the issue where an SQL syntax error occurs due to the non-existence of the REGEXP_LIKE function when attempting to update the company_structure table.": { + "1.5.2": "MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch" } }, "magento/magento2-ee-base": { diff --git a/patches/MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch b/patches/MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch new file mode 100644 index 0000000..33e5683 --- /dev/null +++ b/patches/MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch @@ -0,0 +1,14 @@ +diff --git a/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php b/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php +index 4dcb3dbcb5b3..7ba339a8703e 100644 +--- a/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php ++++ b/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php +@@ -71,7 +71,7 @@ public function apply() + $this->moduleDataSetup->getConnection()->update( + $this->moduleDataSetup->getTable('company_structure'), + ['company_id' => $company['entity_id']], +- ['REGEXP_LIKE(path, ?)' => ++ ['path REGEXP ?' => + '^' . $adminStructureIds[$company['super_user_id']]['structure_id'] . '(/.+)?$'] + ); + } + From ea0693008f9ba1757e783412aff831acdeb2572d Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Tue, 15 Apr 2025 17:21:20 +0530 Subject: [PATCH 70/82] MCLOUD-13605: 1.1.5 release --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9b67a3c..dea6dbc 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.4", + "version": "1.1.5", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From 7a87cd029f71f5c23ec49defe713cf9bc13e0891 Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Thu, 24 Apr 2025 15:43:36 +0530 Subject: [PATCH 71/82] MCLOUD-13628: Cloud release cloud-patches 1.1.6 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index dea6dbc..21d5746 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.5", + "version": "1.1.6", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From 3460a5eebdd55ce6490e9e72af2ab334bca31756 Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Thu, 10 Apr 2025 13:01:19 +0530 Subject: [PATCH 72/82] MCLOUD-13619 : Improve web api async performance --- patches.json | 3 ++ ...ove_web_api_async_performance__2.4.x.patch | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 patches/MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch diff --git a/patches.json b/patches.json index b59583d..fc20e91 100644 --- a/patches.json +++ b/patches.json @@ -298,6 +298,9 @@ ">=2.4.5 <2.4.5-p11": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch", ">=2.4.6 <2.4.6-p9": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch", ">=2.4.7 <2.4.7-p4": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch" + }, + "Patch - Improve-web-api-async-performance": { + ">=2.4.4 <2.4.4-p13 || >=2.4.5 <2.4.5-p12 || >=2.4.6 <2.4.6-p10 || >=2.4.7 <2.4.7-p5 || 2.4.8": "MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch b/patches/MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch new file mode 100644 index 0000000..3073c35 --- /dev/null +++ b/patches/MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch @@ -0,0 +1,50 @@ +diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +index 93555559ac9a1..2f46685c6b117 100644 +--- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php ++++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php +@@ -70,6 +70,11 @@ class InputParamsResolver + */ + private $methodsMap; + ++ /** ++ * @var array ++ */ ++ private array $inputData = []; ++ + /** + * Initialize dependencies. + * +@@ -156,8 +161,14 @@ public function resolve() + */ + public function getInputData() + { ++ if (!empty($this->inputData)) { ++ return $this->inputData; ++ } ++ + if ($this->isBulk === false) { +- return [$this->inputParamsResolver->getInputData()]; ++ $this->inputData = [$this->inputParamsResolver->getInputData()]; ++ ++ return $this->inputData; + } + $inputData = $this->request->getRequestData(); + +@@ -167,7 +178,7 @@ public function getInputData() + $inputData = array_merge($requestBodyParams, $inputData); + } + +- return array_map(function ($singleEntityParams) { ++ $this->inputData = array_map(function ($singleEntityParams) { + if (is_array($singleEntityParams)) { + $singleEntityParams = $this->filterInputData($singleEntityParams); + $singleEntityParams = $this->paramsOverrider->override( +@@ -178,6 +189,8 @@ public function getInputData() + + return $singleEntityParams; + }, $inputData); ++ ++ return $this->inputData; + } + + /** From 1095cb3162396f6d2eb4f45e65b971eb03658196 Mon Sep 17 00:00:00 2001 From: ashvini22326 Date: Mon, 5 May 2025 16:03:12 +0530 Subject: [PATCH 73/82] MCLOUD-13651 Cloud patches release 1.1.7 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 21d5746..0d96a61 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.6", + "version": "1.1.7", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From 48a57a4db4485409742bbcb2ca38b652c83e3c58 Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Wed, 28 May 2025 19:30:03 +0530 Subject: [PATCH 74/82] MCLOUD-13707: Included newer versions for Cloud patches package dependency --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 0d96a61..e8fa441 100644 --- a/composer.json +++ b/composer.json @@ -16,12 +16,12 @@ "composer/composer": "^1.9 || ^2.8 || !=2.2.16", "composer/semver": "@stable", "monolog/monolog": "^2.3 || ^2.7 || ^3.6", - "symfony/config": "^4.4 || ^5.1 || ^5.4 || ^6.4", - "symfony/console": "^4.4 || ^5.1 || ^5.4 || ^6.4", - "symfony/dependency-injection": "^4.4 || ^5.1 || ^5.4 || ^6.4", - "symfony/process": "^4.4 || ^5.1 || ^5.4 || ^6.4", + "symfony/config": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", + "symfony/console": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", + "symfony/dependency-injection": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", + "symfony/process": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", - "symfony/yaml": "^4.4 || ^5.1 || ^5.4 || ^6.4", + "symfony/yaml": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", "magento/quality-patches": "^1.1.0" }, "require-dev": { @@ -30,7 +30,7 @@ "codeception/module-db": "^1.0 || ^3.0", "codeception/module-phpbrowser": "^1.0 || ^3.0", "codeception/module-rest": "^1.2 || ^3.0", - "consolidation/robo": "^1.2 || ^3.0 || ^5.0", + "consolidation/robo": "^1.2 || ^3.0 || ^4.0 || ^5.0", "phpmd/phpmd": "@stable", "phpunit/phpunit": "^10.0", "squizlabs/php_codesniffer": "^3.0" From 0e484f262eb78896d3ade746786987259c349555 Mon Sep 17 00:00:00 2001 From: Deepak Tiwari Date: Wed, 28 May 2025 16:11:00 +0530 Subject: [PATCH 75/82] MCLOUD-13753 : Improve-admin-cache-efficiency --- patches.json | 3 +++ ...mprove-admin-cache-efficiency__2.4.x.patch | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 patches/MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch diff --git a/patches.json b/patches.json index 1b7265a..ced56c6 100644 --- a/patches.json +++ b/patches.json @@ -301,6 +301,9 @@ }, "Patch - Improve-web-api-async-performance": { ">=2.4.4 <2.4.4-p13 || >=2.4.5 <2.4.5-p12 || >=2.4.6 <2.4.6-p10 || >=2.4.7 <2.4.7-p5 || 2.4.8": "MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch" + }, + "Patch for CVE-2025-47110 - Improve-admin-cache-efficiency": { + ">=2.4.4 <2.4.4-p14 || >=2.4.5 <2.4.5-p13 || >=2.4.6 <2.4.6-p11 || >=2.4.7 <2.4.7-p6 || 2.4.8": "MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch b/patches/MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch new file mode 100644 index 0000000..b9af766 --- /dev/null +++ b/patches/MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch @@ -0,0 +1,25 @@ +diff --git a/vendor/magento/module-email/Model/Template/Filter.php b/vendor/magento/module-email/Model/Template/Filter.php +index f5b69484285cc..f86deacad3b4e 100644 +--- a/vendor/magento/module-email/Model/Template/Filter.php ++++ b/vendor/magento/module-email/Model/Template/Filter.php +@@ -1,7 +1,7 @@ + setDataUsingMethod($k, $v); + } + ++ if (!$block->hasData('cache_key')) { ++ $block->setDataUsingMethod('cache_key', $block->getCacheKey()); ++ } ++ + if (isset($blockParameters['output'])) { + $method = $blockParameters['output']; + } From 8d94a1e20e94ee93ef5dd7c783868d3968e87018 Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Thu, 29 May 2025 17:34:38 +0530 Subject: [PATCH 76/82] MCLOUD-13707: Unit test fixes for all PHP versions --- src/Command/Apply.php | 2 +- src/Command/Ece/Apply.php | 2 +- src/Command/Ece/Revert.php | 2 +- src/Command/Revert.php | 2 +- src/Command/Status.php | 2 +- .../Unit/Shell/Command/PatchDriverTest.php | 160 ++++++++++-------- 6 files changed, 92 insertions(+), 78 deletions(-) diff --git a/src/Command/Apply.php b/src/Command/Apply.php index 47b1692..2fb110c 100644 --- a/src/Command/Apply.php +++ b/src/Command/Apply.php @@ -81,7 +81,7 @@ protected function configure() /** * @inheritDoc */ - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): int { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Ece/Apply.php b/src/Command/Ece/Apply.php index 7a1b513..6749962 100644 --- a/src/Command/Ece/Apply.php +++ b/src/Command/Ece/Apply.php @@ -89,7 +89,7 @@ protected function configure() /** * @inheritDoc */ - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): int { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Ece/Revert.php b/src/Command/Ece/Revert.php index 8244635..5065008 100644 --- a/src/Command/Ece/Revert.php +++ b/src/Command/Ece/Revert.php @@ -71,7 +71,7 @@ protected function configure() /** * {@inheritDoc} */ - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): int { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Revert.php b/src/Command/Revert.php index d0bc7e4..7041282 100644 --- a/src/Command/Revert.php +++ b/src/Command/Revert.php @@ -94,7 +94,7 @@ protected function configure() /** * {@inheritDoc} */ - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): int { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Status.php b/src/Command/Status.php index 72df3a0..3f70ed9 100644 --- a/src/Command/Status.php +++ b/src/Command/Status.php @@ -61,7 +61,7 @@ protected function configure() /** * {@inheritDoc} */ - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): int { try { $this->showStatus->run($input, $output); diff --git a/src/Test/Unit/Shell/Command/PatchDriverTest.php b/src/Test/Unit/Shell/Command/PatchDriverTest.php index b1a4b4d..2e9d7a9 100644 --- a/src/Test/Unit/Shell/Command/PatchDriverTest.php +++ b/src/Test/Unit/Shell/Command/PatchDriverTest.php @@ -1,63 +1,56 @@ baseDir = dirname(__DIR__, 5) . '/tests/unit/'; $this->cwd = $this->baseDir . 'var/'; - $processFactory = $this->createMock(ProcessFactory::class); - $processFactory->method('create') - ->willReturnCallback( - function (array $cmd, ?string $input = null) { - return new Process( - $cmd, - $this->cwd, - null, - $input - ); - } - ); - $this->command = new PatchDriver( - $processFactory - ); + $this->processFactoryMock = $this->createMock(ProcessFactory::class); } /** - * @inheritDoc + * Clean up files after tests. + * + * @return void */ protected function tearDown(): void { @@ -70,81 +63,101 @@ protected function tearDown(): void } /** - * Tests that patch is applied + * Test successful patch apply. + * + * @return void */ - public function testApply() + public function testApply(): void { $this->copyFileToWorkingDir($this->getFixtureFile('file1.md')); $patchContent = $this->getFileContent($this->getFixtureFile('file1.patch')); - $this->command->apply($patchContent); + + $this->processFactoryMock->method('create')->willReturnCallback( + function (array $cmd, ?string $input = null) { + return new Process($cmd, $this->cwd, null, $input); + } + ); + + $command = new PatchDriver($this->processFactoryMock); + $command->apply($patchContent); + $expected = $this->getFileContent($this->getFixtureFile('file1_applied_patch.md')); $actual = $this->getFileContent($this->getVarFile('file1.md')); + $this->assertEquals($expected, $actual); } /** - * Tests that patch is not applied to any target files if an error occurs + * Test patch apply failure handling. + * + * @return void */ - public function testApplyFailure() + public function testApplyFailure(): void { $this->copyFileToWorkingDir($this->getFixtureFile('file1.md')); $this->copyFileToWorkingDir($this->getFixtureFile('file2_applied_patch.md'), 'file2.md'); $patchContent = $this->getFileContent($this->getFixtureFile('file1_and_file2.patch')); - $exception = null; - try { - $this->command->apply($patchContent); - } catch (PatchCommandException $e) { - $exception = $e; - } - $this->assertNotNull($exception); - $expected = $this->getFileContent($this->getFixtureFile('file1.md')); - $actual = $this->getFileContent($this->getVarFile('file1.md')); - $this->assertEquals($expected, $actual); - $expected = $this->getFileContent($this->getFixtureFile('file2_applied_patch.md')); - $actual = $this->getFileContent($this->getVarFile('file2.md')); - $this->assertEquals($expected, $actual); + + $processMock = $this->createMock(Process::class); + $processMock->method('mustRun')->willThrowException(new ProcessFailedException($processMock)); + + $this->processFactoryMock->method('create')->willReturn($processMock); + $command = new PatchDriver($this->processFactoryMock); + + $this->expectException(DriverException::class); + $command->apply($patchContent); } /** - * Tests that patch is reverted + * Test successful patch revert. + * + * @return void */ - public function testRevert() + public function testRevert(): void { $this->copyFileToWorkingDir($this->getFixtureFile('file1_applied_patch.md'), 'file1.md'); $patchContent = $this->getFileContent($this->getFixtureFile('file1.patch')); - $this->command->revert($patchContent); + + $this->processFactoryMock->method('create')->willReturnCallback( + function (array $cmd, ?string $input = null) { + return new Process($cmd, $this->cwd, null, $input); + } + ); + + $command = new PatchDriver($this->processFactoryMock); + $command->revert($patchContent); + $expected = $this->getFileContent($this->getFixtureFile('file1.md')); $actual = $this->getFileContent($this->getVarFile('file1.md')); + $this->assertEquals($expected, $actual); } /** - * Tests that patch is not reverted in any target files if an error occurs + * Test patch revert failure handling + * + * @return void */ - public function testRevertFailure() + public function testRevertFailure(): void { $this->copyFileToWorkingDir($this->getFixtureFile('file1_applied_patch.md'), 'file1.md'); $this->copyFileToWorkingDir($this->getFixtureFile('file2.md')); $patchContent = $this->getFileContent($this->getFixtureFile('file1_and_file2.patch')); - $exception = null; - try { - $this->command->revert($patchContent); - } catch (PatchCommandException $e) { - $exception = $e; - } - $this->assertNotNull($exception); - $expected = $this->getFileContent($this->getFixtureFile('file1_applied_patch.md')); - $actual = $this->getFileContent($this->getVarFile('file1.md')); - $this->assertEquals($expected, $actual); - $expected = $this->getFileContent($this->getFixtureFile('file2.md')); - $actual = $this->getFileContent($this->getVarFile('file2.md')); - $this->assertEquals($expected, $actual); + + $processMock = $this->createMock(Process::class); + $processMock->method('mustRun')->willThrowException(new ProcessFailedException($processMock)); + + $this->processFactoryMock->method('create')->willReturn($processMock); + $command = new PatchDriver($this->processFactoryMock); + + $this->expectException(DriverException::class); + $command->revert($patchContent); } /** - * Get file path in var directory + * Get full path to a file in the test working directory. * - * @param string $name + * @param string $name * @return string */ private function getVarFile(string $name): string @@ -153,9 +166,9 @@ private function getVarFile(string $name): string } /** - * Get file path in files directory + * Get full path to a fixture file. * - * @param string $name + * @param string $name * @return string */ private function getFixtureFile(string $name): string @@ -164,9 +177,9 @@ private function getFixtureFile(string $name): string } /** - * Get the file content + * Get content from a file. * - * @param string $path + * @param string $path * @return string */ private function getFileContent(string $path): string @@ -175,12 +188,13 @@ private function getFileContent(string $path): string } /** - * Copy file to working directory + * Copy a file to the test working directory. * - * @param string $path - * @param string|null $name + * @param string $path + * @param string|null $name + * @return void */ - private function copyFileToWorkingDir(string $path, ?string $name = null) + private function copyFileToWorkingDir(string $path, ?string $name = null): void { $name = $name ?? basename($path); copy($path, $this->getVarFile($name)); From 3f659d8f8e864dae8ae3d74f76edb68cafa2de05 Mon Sep 17 00:00:00 2001 From: Manohar Prabhutendolkar Date: Thu, 29 May 2025 18:29:52 +0530 Subject: [PATCH 77/82] MCLOUD-13752: Improve category view --- patches.json | 3 + ...5-47109_Improve_category_view__2.4.8.patch | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 patches/MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch diff --git a/patches.json b/patches.json index 1b7265a..ffd95c9 100644 --- a/patches.json +++ b/patches.json @@ -301,6 +301,9 @@ }, "Patch - Improve-web-api-async-performance": { ">=2.4.4 <2.4.4-p13 || >=2.4.5 <2.4.5-p12 || >=2.4.6 <2.4.6-p10 || >=2.4.7 <2.4.7-p5 || 2.4.8": "MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch" + }, + "Patch for CVE-2025-47109 - Improve-category-view": { + "2.4.8": "MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch" } }, "magento/module-paypal": { diff --git a/patches/MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch b/patches/MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch new file mode 100644 index 0000000..e042d0b --- /dev/null +++ b/patches/MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch @@ -0,0 +1,59 @@ +diff --git a/vendor/magento/module-catalog/Helper/Category.php b/vendor/magento/module-catalog/Helper/Category.php +index fe511d40e9caa..761dc6f62adda 100644 +--- a/vendor/magento/module-catalog/Helper/Category.php ++++ b/vendor/magento/module-catalog/Helper/Category.php +@@ -10,8 +10,10 @@ + use Magento\Catalog\Model\CategoryFactory; + use Magento\Framework\App\Helper\AbstractHelper; + use Magento\Framework\App\Helper\Context; ++use Magento\Framework\App\ObjectManager; + use Magento\Framework\Data\CollectionFactory; + use Magento\Framework\Data\Tree\Node\Collection; ++use Magento\Framework\Escaper; + use Magento\Framework\Exception\NoSuchEntityException; + use Magento\Framework\ObjectManager\ResetAfterRequestInterface; + use Magento\Store\Model\ScopeInterface; +@@ -63,24 +65,33 @@ class Category extends AbstractHelper implements ResetAfterRequestInterface + */ + protected $categoryRepository; + ++ /** ++ * @var Escaper|null ++ */ ++ private ?Escaper $escaper; ++ + /** + * @param Context $context + * @param CategoryFactory $categoryFactory + * @param StoreManagerInterface $storeManager + * @param CollectionFactory $dataCollectionFactory + * @param CategoryRepositoryInterface $categoryRepository ++ * @param Escaper|null $escaper + */ + public function __construct( + Context $context, + CategoryFactory $categoryFactory, + StoreManagerInterface $storeManager, + CollectionFactory $dataCollectionFactory, +- CategoryRepositoryInterface $categoryRepository ++ CategoryRepositoryInterface $categoryRepository, ++ ?Escaper $escaper = null + ) { + $this->_categoryFactory = $categoryFactory; + $this->_storeManager = $storeManager; + $this->_dataCollectionFactory = $dataCollectionFactory; + $this->categoryRepository = $categoryRepository; ++ $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); ++ + parent::__construct($context); + } + +@@ -204,6 +215,7 @@ public function getCanonicalUrl(string $categoryUrl): string + if ($params && isset($params['p'])) { + $categoryUrl = $categoryUrl . '?p=' . $params['p']; + } +- return $categoryUrl; ++ ++ return $this->escaper->escapeUrl($categoryUrl); + } + } From cbd4c9354530230e0389befc11fe889c0f64ac1a Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Mon, 2 Jun 2025 17:22:56 +0530 Subject: [PATCH 78/82] Cloud release cloud-patches 1.1.8 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e8fa441..5d7a72e 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.7", + "version": "1.1.8", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From 7eab8020ba71343b268063fb41d32bdbe60bb000 Mon Sep 17 00:00:00 2001 From: ashvini22326 Date: Mon, 9 Jun 2025 16:04:23 +0530 Subject: [PATCH 79/82] MCLOUD-13801: Cloud patches release 1.1.9 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5d7a72e..565647c 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.8", + "version": "1.1.9", "license": "OSL-3.0", "repositories": { "repo.magento.com": { From 04d69a52983a96dd1ab4e0c0280312079ed4685c Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Wed, 23 Jul 2025 16:32:42 +0530 Subject: [PATCH 80/82] MCLOUD-13312: Added PHP8.4 functional test --- .../Acceptance/Acceptance84Cest.php | 24 +++++++++++++++++++ .../Acceptance/PatchApplierCest.php | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/Test/Functional/Acceptance/Acceptance84Cest.php diff --git a/src/Test/Functional/Acceptance/Acceptance84Cest.php b/src/Test/Functional/Acceptance/Acceptance84Cest.php new file mode 100644 index 0000000..fac3002 --- /dev/null +++ b/src/Test/Functional/Acceptance/Acceptance84Cest.php @@ -0,0 +1,24 @@ + '2.4.8', 'magentoVersion' => '2.4.8'], + ]; + } +} diff --git a/src/Test/Functional/Acceptance/PatchApplierCest.php b/src/Test/Functional/Acceptance/PatchApplierCest.php index f5e58e1..07730b8 100644 --- a/src/Test/Functional/Acceptance/PatchApplierCest.php +++ b/src/Test/Functional/Acceptance/PatchApplierCest.php @@ -10,7 +10,7 @@ use Magento\CloudDocker\Test\Functional\Codeception\Docker; /** - * @group php83 + * @group php84 */ class PatchApplierCest extends AbstractCest { @@ -21,7 +21,7 @@ public function _before(\CliTester $I): void { parent::_before($I); - $this->prepareTemplate($I, '2.4.7'); + $this->prepareTemplate($I, '2.4.8'); $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); } From 20c7af7edf048b7caf8cf0c78f74e4a33a1c26ca Mon Sep 17 00:00:00 2001 From: Prateek Karanpuria Date: Fri, 25 Jul 2025 17:15:25 +0530 Subject: [PATCH 81/82] MCLOUD-13312: Segregated PatchApplierCest based on PHP & Magento versions --- .../Acceptance/PatchApplier83Cest.php | 24 +++++++++ .../Acceptance/PatchApplier84Cest.php | 24 +++++++++ .../Acceptance/PatchApplierCest.php | 50 ++++++++++++++++--- 3 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 src/Test/Functional/Acceptance/PatchApplier83Cest.php create mode 100644 src/Test/Functional/Acceptance/PatchApplier84Cest.php diff --git a/src/Test/Functional/Acceptance/PatchApplier83Cest.php b/src/Test/Functional/Acceptance/PatchApplier83Cest.php new file mode 100644 index 0000000..49e3e4b --- /dev/null +++ b/src/Test/Functional/Acceptance/PatchApplier83Cest.php @@ -0,0 +1,24 @@ + '2.4.7', 'magentoVersion' => '2.4.7'], + ]; + } +} diff --git a/src/Test/Functional/Acceptance/PatchApplier84Cest.php b/src/Test/Functional/Acceptance/PatchApplier84Cest.php new file mode 100644 index 0000000..c14a280 --- /dev/null +++ b/src/Test/Functional/Acceptance/PatchApplier84Cest.php @@ -0,0 +1,24 @@ + '2.4.8', 'magentoVersion' => '2.4.8'], + ]; + } +} diff --git a/src/Test/Functional/Acceptance/PatchApplierCest.php b/src/Test/Functional/Acceptance/PatchApplierCest.php index 07730b8..46b526d 100644 --- a/src/Test/Functional/Acceptance/PatchApplierCest.php +++ b/src/Test/Functional/Acceptance/PatchApplierCest.php @@ -10,28 +10,43 @@ use Magento\CloudDocker\Test\Functional\Codeception\Docker; /** - * @group php84 + * Abstract PatchApplierCest + * + * @abstract */ -class PatchApplierCest extends AbstractCest +abstract class PatchApplierCest extends AbstractCest { /** - * @param \CliTester $I + * Prepares the test environment before each test. + * + * @param \CliTester $I The CLI tester instance. + * @throws \Robo\Exception\TaskException */ public function _before(\CliTester $I): void { parent::_before($I); - - $this->prepareTemplate($I, '2.4.8'); - $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); } /** + * Tests applying an existing patch to a target file. + * * @param \CliTester $I + * @param \Codeception\Example $data The example data for the test. + * Expected structure: + * [ + * 'templateVersion' => string, + * 'magentoVersion' => string|null (optional) + * ] * @throws \Robo\Exception\TaskException + * @dataProvider patchesDataProvider */ - public function testApplyingPatch(\CliTester $I): void + public function testApplyingPatch(\CliTester $I, \Codeception\Example $data): void { + $this->prepareTemplate($I, $data['templateVersion'], $data['magentoVersion'] ?? null); + $I->generateDockerCompose('--mode=production'); + + $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); $I->copyFileToWorkDir('files/patches/target_file.md', 'target_file.md'); $I->copyFileToWorkDir('files/patches/patch.patch', 'm2-hotfixes/patch.patch'); @@ -47,12 +62,25 @@ public function testApplyingPatch(\CliTester $I): void } /** + * Tests that an existing patch is not applied again. + * * @param \CliTester $I + * @param \Codeception\Example $data The example data for the test. + * Expected structure: + * [ + * 'templateVersion' => string, + * 'magentoVersion' => string|null (optional) + * ] * @throws \Robo\Exception\TaskException + * @dataProvider patchesDataProvider */ - public function testApplyingExistingPatch(\CliTester $I): void + public function testApplyingExistingPatch(\CliTester $I, \Codeception\Example $data): void { + $this->prepareTemplate($I, $data['templateVersion'], $data['magentoVersion'] ?? null); + $I->generateDockerCompose('--mode=production'); + + $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); $I->copyFileToWorkDir('files/patches/target_file_applied_patch.md', 'target_file.md'); $I->copyFileToWorkDir('files/patches/patch.patch', 'm2-hotfixes/patch.patch'); @@ -68,4 +96,10 @@ public function testApplyingExistingPatch(\CliTester $I): void $I->grabFileContent('/init/var/log/cloud.log', Docker::BUILD_CONTAINER) ); } + + /** + * Returns the data provider for patches. + * @return array + */ + abstract protected function patchesDataProvider(): array; } From 5770260c44677e7a485a9988ed42f416e462203e Mon Sep 17 00:00:00 2001 From: vishalakshi Date: Wed, 6 Aug 2025 15:21:56 +0530 Subject: [PATCH 82/82] MCLOUD-13940: Cloud tools August 2025 release --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 565647c..f9f9caa 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.9", + "version": "1.1.10", "license": "OSL-3.0", "repositories": { "repo.magento.com": {