diff --git a/Console/ReencryptColumn.php b/Console/ReencryptColumn.php index 2fcd439..9779600 100644 --- a/Console/ReencryptColumn.php +++ b/Console/ReencryptColumn.php @@ -6,6 +6,7 @@ use Magento\Framework\App\CacheInterface; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; use Magento\Framework\Console\Cli; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -21,14 +22,17 @@ class ReencryptColumn extends Command public const INPUT_KEY_COLUMN = 'column'; /** - * @param DeploymentConfig $deploymentConfig - * @param ResourceConnection $resourceConnection - * @param EncryptorInterface $encryptor - * @param CacheInterface $cache + * @param Magento\Framework\App\DeploymentConfig $deploymentConfig + * @param Magento\Framework\App\ResourceConnection $resourceConnection + * @param Magento\Framework\Serialize\Serializer\Json $jsonSerializer + * @param Magento\Framework\Encryption\EncryptorInterface $encryptor + * @param Magento\Framework\App\CacheInterface $cache + * @return void */ public function __construct( private readonly DeploymentConfig $deploymentConfig, private readonly ResourceConnection $resourceConnection, + private readonly JsonSerializer $jsonSerializer, private readonly EncryptorInterface $encryptor, private readonly CacheInterface $cache ) { @@ -105,20 +109,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!strlen($column)) { throw new \Exception('Provide an column'); } - $output->writeln("Looking for '$column' in '$table', identified by '$identifier'"); - + $jsonField = null; + if (strpos($column, '.') !== false) { + list($column, $jsonField) = explode('.', $column); + $output->writeln( + "Looking for JSON field '$jsonField.$column' in '$table', identified by '$identifier'" + ); + } else { + $output->writeln("Looking for '$column' in '$table', identified by '$identifier'"); + } /** * @see \Magento\Framework\Model\ResourceModel\Db\AbstractDb::_getLoadSelect() */ $tableName = $this->resourceConnection->getTableName($table); $connection = $this->resourceConnection->getConnection(); $field = $connection->quoteIdentifier(sprintf('%s.%s', $tableName, $column)); - $select = $connection->select() - ->from($tableName, [$identifier, "$column"]) - ->where("($field LIKE '_:_:____%' OR $field LIKE '__:_:____%')") - ->where("$field NOT LIKE ?", "$latestKeyNumber:_:__%"); - + ->from($tableName, [$identifier, "$column"]); + if ($jsonField === null) { + $select = $select->where("($field LIKE '_:_:____%' OR $field LIKE '__:_:____%')") + ->where("$field NOT LIKE ?", "$latestKeyNumber:_:__%"); + } else { + $select = $select->where("($field LIKE '{%_:_:____%}' OR $field LIKE '{%__:_:____%}')") + ->where("$field NOT LIKE ?", "{%$latestKeyNumber:_:__%}"); + } $result = $connection->fetchAll($select); if (empty($result)) { $output->writeln('No old entries found'); @@ -127,14 +141,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int $connection->beginTransaction(); foreach ($result as $row) { $output->writeln(str_pad('', 120, '#')); - $output->writeln("$identifier: {$row[$identifier]}"); $value = $row[$column]; + $fieldData = []; + if ($jsonField !== null) { + $fieldData = $this->jsonSerializer->unserialize($value); + $value = $fieldData[$jsonField] ?? ''; + } + if ($value === '') { + continue; + } + $output->writeln("$identifier: {$row[$identifier]}"); $output->writeln("ciphertext_old: " . $value); $valueDecrypted = $this->encryptor->decrypt($value); $output->writeln("plaintext: " . $valueDecrypted); $valueEncrypted = $this->encryptor->encrypt($valueDecrypted); $output->writeln("ciphertext_new: " . $valueEncrypted); + if ($jsonField !== null) { + $fieldData[$jsonField] = $valueEncrypted; + $valueEncrypted = $this->jsonSerializer->serialize($fieldData); + } + if ($input->getOption(self::INPUT_KEY_FORCE)) { $connection->update( $tableName, diff --git a/README.md b/README.md index 4586c33..9bd48e2 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ Done ## bin/magento gene:encryption-key-manager:reencrypt-column -This allows you to target a specific column for re-encryption. +This allows you to target a specific column for re-encryption. If the column contains JSON, you can target it using dot notation: `column.field`. This command runs in dry run mode by default, do that as a first pass to see the changes that will be made. When you are happy run with `--force`. diff --git a/dev/test.sh b/dev/test.sh index a844c16..75be0a4 100755 --- a/dev/test.sh +++ b/dev/test.sh @@ -30,6 +30,13 @@ FAKE_RP_TOKEN=$(vendor/bin/n98-magerun2 dev:encrypt 'abc123') vendor/bin/n98-magerun2 db:query "update admin_user set rp_token='$FAKE_RP_TOKEN' where username='$ADMIN'" echo "Generated FAKE_RP_TOKEN=$FAKE_RP_TOKEN and assigned to $ADMIN" +echo "Generating a fake json column" +FAKE_JSON_PASSWORD=$(vendor/bin/n98-magerun2 dev:encrypt 'jsonpasswordabc123') +FAKE_JSON_PAYLOAD="{\"user\": \"foobar\", \"password\": \"$FAKE_JSON_PASSWORD\", \"request_url\": \"\"}" +vendor/bin/n98-magerun2 db:query 'DROP TABLE IF EXISTS fake_json_table; CREATE TABLE fake_json_table (id INT AUTO_INCREMENT PRIMARY KEY, text_column TEXT);' +vendor/bin/n98-magerun2 db:query "insert into fake_json_table(text_column) values ('$FAKE_JSON_PAYLOAD');" +vendor/bin/n98-magerun2 db:query "select * from fake_json_table"; + echo "";echo ""; echo "Verifying commands need to use --force" @@ -142,6 +149,18 @@ php bin/magento gene:encryption-key-manager:reencrypt-column admin_user user_id echo "PASS" echo "";echo ""; +echo "Running reencrypt-column on JSON column" +php bin/magento gene:encryption-key-manager:reencrypt-column fake_json_table id text_column.password --force > test.txt +cat test.txt +grep -q "$FAKE_JSON_PASSWORD" test.txt +grep -q jsonpasswordabc123 test.txt +echo "PASS" +echo "";echo ""; +echo "Running reencrypt-column on JSON column - again to verify it was all processed" +php bin/magento gene:encryption-key-manager:reencrypt-column fake_json_table id text_column.password --force | grep --context 999 'No old entries found' +echo "PASS" +echo "";echo ""; + echo "Running invalidate" php bin/magento gene:encryption-key-manager:invalidate --force grep -q invalidated_key app/etc/env.php