Skip to content
Merged
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
51 changes: 39 additions & 12 deletions Console/ReencryptColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
) {
Expand Down Expand Up @@ -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');
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
19 changes: 19 additions & 0 deletions dev/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down