Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue #39530 to avoid regenerating admin grid flat table #39540

Open
wants to merge 16 commits into
base: 2.4-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 88 additions & 19 deletions app/code/Magento/EncryptionKey/Model/ResourceModel/Key/Change.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
use Magento\Framework\Config\Data\ConfigData;
use Magento\Framework\Config\File\ConfigFilePool;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\Encryption\Encryptor;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
use Magento\Framework\Math\Random;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\Indexer\ConfigInterface;
use Magento\Framework\Json\EncoderInterface;
use Magento\Indexer\Model\ResourceModel\Indexer\State\CollectionFactory;

/**
* Encryption key changer resource model
Expand All @@ -29,9 +33,9 @@
* @api
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
* @since 100.0.2
* @deprecated
* @see Extensible Reencryption Mechanism
* @see Extensible Reencryption Mechanism
*/
class Change extends AbstractDb
{
Expand Down Expand Up @@ -66,19 +70,45 @@ class Change extends AbstractDb
/**
* Random string generator
*
* @var Random
* @var Random
* @since 100.0.4
*/
protected $random;

/**
* @param Context $context
* @param Filesystem $filesystem
* @param Structure $structure
* @param EncryptorInterface $encryptor
* @param Writer $writer
* @param Random $random
* @param string $connectionName
* Indexer Configuration
*
* @var IndexerConfig
*/
protected $indexerConfig;

/**
* Json Encoder
*
* @var Encoder
*/
protected $encoder;

/**
* Indexer State Collection Factory
*
* @var IndexerStateCollection
*/
protected $indexerStateCollection;

/**
* @param Context $context
* @param Filesystem $filesystem
* @param Structure $structure
* @param EncryptorInterface $encryptor
* @param Writer $writer
* @param Random $random
* @param ConfigInterface $indexerConfig
* @param EncoderInterface $encoder
* @param CollectionFactory $indexerStateCollection
* @param string $connectionName
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
Context $context,
Expand All @@ -87,6 +117,9 @@ public function __construct(
EncryptorInterface $encryptor,
Writer $writer,
Random $random,
ConfigInterface $indexerConfig,
EncoderInterface $encoder,
CollectionFactory $indexerStateCollection,
$connectionName = null
) {
$this->encryptor = clone $encryptor;
Expand All @@ -95,6 +128,9 @@ public function __construct(
$this->structure = $structure;
$this->writer = $writer;
$this->random = $random;
$this->indexerConfig = $indexerConfig;
$this->encoder = $encoder;
$this->indexerStateCollection = $indexerStateCollection;
}

/**
Expand All @@ -110,11 +146,11 @@ protected function _construct()
/**
* Change encryption key
*
* @param string|null $key
* @return null|string
* @throws FileSystemException|LocalizedException|Exception
* @param string|null $key
* @return null|string
* @throws FileSystemException|LocalizedException|Exception
* @deprecated
* @see Extensible Reencryption Mechanism
* @see Extensible Reencryption Mechanism
*/
public function changeEncryptionKey($key = null)
{
Expand All @@ -139,6 +175,7 @@ public function changeEncryptionKey($key = null)
try {
$this->_reEncryptSystemConfigurationValues();
$this->_reEncryptCreditCardNumbers();
$this->updateIndexersHash();
$this->writer->saveConfig($configData);
$this->commit();
return $key;
Expand All @@ -151,14 +188,16 @@ public function changeEncryptionKey($key = null)
/**
* Gather all encrypted system config values and re-encrypt them
*
* @return void
* @return void
* @deprecated
* @see Extensible Reencryption Mechanism
* @see Extensible Reencryption Mechanism
*/
protected function _reEncryptSystemConfigurationValues()
{
// look for encrypted node entries in all system.xml files
/** @var Structure $configStructure */
/**
* @var Structure $configStructure
*/
$configStructure = $this->structure;
$paths = $configStructure->getFieldPathsByAttribute(
'backend_model',
Expand Down Expand Up @@ -188,9 +227,9 @@ protected function _reEncryptSystemConfigurationValues()
/**
* Gather saved credit card numbers from sales order payments and re-encrypt them
*
* @return void
* @return void
* @deprecated
* @see Extensible Reencryption Mechanism
* @see Extensible Reencryption Mechanism
*/
protected function _reEncryptCreditCardNumbers()
{
Expand All @@ -207,4 +246,34 @@ protected function _reEncryptCreditCardNumbers()
);
}
}

/**
* Refresh the indexer hash to avoid grid data regeneration
*
* @return void
*/
protected function updateIndexersHash()
{

$stateIndexers = [];
$stateCollection = $this->indexerStateCollection->create();
foreach ($stateCollection->getItems() as $state) {
/**
* @var \Magento\Indexer\Model\Indexer\State $state
*/
$stateIndexers[$state->getIndexerId()] = $state;
}

foreach ($this->indexerConfig->getIndexers() as $indexerId => $indexerConfig) {
$newHashConfig = $this->encryptor->hash(
$this->encoder->encode($indexerConfig),
Encryptor::HASH_VERSION_MD5
);

if (isset($stateIndexers[$indexerId])) {
$stateIndexers[$indexerId]->setHashConfig($newHashConfig);
$stateIndexers[$indexerId]->save();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?php

/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
* Copyright 2015 Adobe
* All Rights Reserved.
*/

declare(strict_types=1);

namespace Magento\EncryptionKey\Test\Unit\Model\ResourceModel\Key;
Expand All @@ -22,6 +24,11 @@
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Magento\Framework\Indexer\ConfigInterface;
use Magento\Framework\Json\EncoderInterface;
use Magento\Indexer\Model\ResourceModel\Indexer\State\Collection as StateCollection;
use Magento\Indexer\Model\ResourceModel\Indexer\State\CollectionFactory;
use Magento\Indexer\Model\Indexer\State;

/**
* Test Class For Magento\EncryptionKey\Model\ResourceModel\Key\Change
Expand Down Expand Up @@ -63,6 +70,15 @@ class ChangeTest extends TestCase
/** @var Change */
protected $model;

/** @var ConfigInterface|MockObject */
protected $indexerConfigMock;

/** @var EncoderInterface|MockObject */
protected $encoderMock;

/** @var CollectionFactory|MockObject */
protected $indexerStateCollectionMock;

protected function setUp(): void
{
$this->encryptMock = $this->getMockBuilder(EncryptorInterface::class)
Expand Down Expand Up @@ -98,6 +114,17 @@ protected function setUp(): void
->disableOriginalConstructor()
->getMock();
$this->randomMock = $this->createMock(Random::class);
$this->indexerConfigMock = $this->getMockBuilder(ConfigInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
$this->encoderMock = $this->getMockBuilder(EncoderInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
$this->indexerStateCollectionMock = $this->getMockBuilder(CollectionFactory::class)
->disableOriginalConstructor()
->onlyMethods(['create'])
->addMethods(['getItems'])
->getMock();

$helper = new ObjectManager($this);

Expand All @@ -112,12 +139,19 @@ protected function setUp(): void
'resource' => $this->resourceMock,
'transactionManager' => $this->transactionMock,
'relationProcessor' => $this->objRelationMock,
'random' => $this->randomMock
'random' => $this->randomMock,
'indexerConfig' => $this->indexerConfigMock,
'encoder' => $this->encoderMock,
'indexerStateCollection' => $this->indexerStateCollectionMock
]
);
}

private function setUpChangeEncryptionKey()
/**
* @param array $indexersData
* @param array $states
*/
private function setUpChangeEncryptionKey(array $indexersData, array $states)
{
$paths = ['path1', 'path2'];
$table = ['item1', 'item2'];
Expand All @@ -138,19 +172,50 @@ private function setUpChangeEncryptionKey()
$this->selectMock->expects($this->any())->method('update')->willReturnSelf();
$this->writerMock->expects($this->once())->method('saveConfig');
$this->adapterMock->expects($this->once())->method('getTransactionLevel')->willReturn(1);

$indexerStateCollection = $this->getMockBuilder(StateCollection::class)
->disableOriginalConstructor()
->getMock();

$this->indexerStateCollectionMock->expects($this->once())
->method('create')->willReturn($indexerStateCollection);

$finalStates = [];

foreach ($states as $key => $state) {
if (is_callable($state)) {
$finalStates[$key] = $state($this);
}
}

$indexerStateCollection->method('getItems')
->willReturn($finalStates);

$this->indexerConfigMock->expects($this->any())->method('getIndexers')->willReturn($indexersData);
}

public function testChangeEncryptionKey()
/**
* @param array $indexersData
* @param array $states
* @dataProvider loadDataDataProvider
*/
public function testChangeEncryptionKey(array $indexersData, array $states)
{
$this->setUpChangeEncryptionKey();

$this->setUpChangeEncryptionKey($indexersData, $states);
$this->randomMock->expects($this->never())->method('getRandomBytes');
$key = 'key';
$this->assertEquals($key, $this->model->changeEncryptionKey($key));
}

public function testChangeEncryptionKeyAutogenerate()
/**
* @param array $indexersData
* @param array $states
* @dataProvider loadDataDataProvider
*/
public function testChangeEncryptionKeyAutogenerate(array $indexersData, array $states)
{
$this->setUpChangeEncryptionKey();
$this->setUpChangeEncryptionKey($indexersData, $states);
$this->randomMock->expects($this->once())->method('getRandomBytes')->willReturn('abc');
$this->assertEquals(
ConfigOptionsListConstants::STORE_KEY_ENCODED_RANDOM_STRING_PREFIX . 'abc',
Expand All @@ -171,4 +236,48 @@ public function testChangeEncryptionKeyThrowsException()

$this->fail('An expected exception was not signaled.');
}

/**
* @param array $data
* @return MockObject|State
*/
private function getStateMock(array $data = [])
{
/** @var MockObject|State $state */
$state = $this->getMockBuilder(State::class)
->disableOriginalConstructor()
->getMock();
if (isset($data['indexer_id'])) {
$state->method('getIndexerId')
->willReturn($data['indexer_id']);
}

return $state;
}

/**
* @return array
*/
public static function loadDataDataProvider()
{
return [
[
'indexersData' => [
'indexer_2' => [
'indexer_id' => 'indexer_2',
],
'indexer_3' => [
'indexer_id' => 'indexer_3',
],
'indexer_1' => [
'indexer_id' => 'indexer_1',
],
],
'states' => [
'indexer_2' => static fn (self $testCase) => $testCase->getStateMock(['indexer_id' => 'indexer_2']),
'indexer_3' => static fn (self $testCase) => $testCase->getStateMock(['indexer_id' => 'indexer_3']),
],
]
];
}
}
3 changes: 2 additions & 1 deletion app/code/Magento/EncryptionKey/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"php": "~8.2.0||~8.3.0||~8.4.0",
"magento/framework": "*",
"magento/module-backend": "*",
"magento/module-config": "*"
"magento/module-config": "*",
"magento/module-indexer": "*"
},
"type": "magento2-module",
"license": [
Expand Down