diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
index d6e055c43322..e9d17b5c70dd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
@@ -39,7 +39,7 @@
-
+
@@ -60,6 +60,7 @@
+
@@ -78,7 +79,7 @@
-
+
@@ -88,6 +89,7 @@
+
diff --git a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php
index 89f6be052566..e94992ae26b6 100644
--- a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php
+++ b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php
@@ -78,7 +78,7 @@ protected function _construct()
$this->buttonList->add(
'insert_files',
- ['class' => 'save no-display primary', 'label' => __('Add Selected'), 'type' => 'button'],
+ ['class' => 'save no-display action-primary', 'label' => __('Add Selected'), 'type' => 'button'],
0,
0,
'header'
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml
index 8c1d17c8d9be..2daa1cbeca80 100644
--- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml
@@ -17,7 +17,6 @@
-
diff --git a/app/code/Magento/Config/Model/Config/Structure.php b/app/code/Magento/Config/Model/Config/Structure.php
index 5a6dbc8e3189..5c74220051ba 100644
--- a/app/code/Magento/Config/Model/Config/Structure.php
+++ b/app/code/Magento/Config/Model/Config/Structure.php
@@ -281,6 +281,10 @@ protected function _createEmptyElement(array $pathParts)
public function getFieldPathsByAttribute($attributeName, $attributeValue)
{
$result = [];
+ if (empty($this->_data['sections'])) {
+ return $result;
+ }
+
foreach ($this->_data['sections'] as $section) {
if (!isset($section['children'])) {
continue;
diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php
index 39b2f772c8e8..8ffa5839a125 100644
--- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php
+++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php
@@ -258,6 +258,7 @@ public function addFieldsMapping(array $fields, $index, $entityType)
'match_mapping_type' => 'string',
'mapping' => [
'type' => 'float',
+ 'store' => true,
],
],
],
diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php
new file mode 100644
index 000000000000..a1fcbeb06148
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php
@@ -0,0 +1,251 @@
+connectionManager = $connectionManager;
+ $this->fieldMapper = $fieldMapper;
+ $this->clientConfig = $clientConfig;
+ $this->fieldName = $fieldName;
+ $this->storeId = $storeId;
+ $this->entityIds = $entityIds;
+ $this->searchIndexNameResolver = $searchIndexNameResolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function load($limit, $offset = null, $lower = null, $upper = null)
+ {
+ $from = $to = [];
+ if ($lower) {
+ $from = ['gte' => $lower - self::DELTA];
+ }
+ if ($upper) {
+ $to = ['lt' => $upper - self::DELTA];
+ }
+
+ $requestQuery = $this->prepareBaseRequestQuery($from, $to);
+ $requestQuery = array_merge_recursive(
+ $requestQuery,
+ ['body' => ['stored_fields' => [$this->fieldName], 'size' => $limit]]
+ );
+
+ if ($offset) {
+ $requestQuery['body']['from'] = $offset;
+ }
+
+ $queryResult = $this->connectionManager->getConnection()
+ ->query($requestQuery);
+
+ return $this->arrayValuesToFloat($queryResult['hits']['hits'], $this->fieldName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadPrevious($data, $index, $lower = null)
+ {
+ if ($lower) {
+ $from = ['gte' => $lower - self::DELTA];
+ }
+ if ($data) {
+ $to = ['lt' => $data - self::DELTA];
+ }
+
+ $requestQuery = $this->prepareBaseRequestQuery($from, $to);
+ $requestQuery = array_merge_recursive(
+ $requestQuery,
+ ['size' => 0]
+ );
+
+ $queryResult = $this->connectionManager->getConnection()
+ ->query($requestQuery);
+
+ $offset = $queryResult['hits']['total'];
+ if (!$offset) {
+ return false;
+ }
+
+ return $this->load($index - $offset + 1, $offset - 1, $lower);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadNext($data, $rightIndex, $upper = null)
+ {
+ $from = ['gt' => $data + self::DELTA];
+ $to = ['lt' => $data - self::DELTA];
+
+ $requestCountQuery = $this->prepareBaseRequestQuery($from, $to);
+ $requestCountQuery = array_merge_recursive(
+ $requestCountQuery,
+ ['size' => 0]
+ );
+
+ $queryCountResult = $this->connectionManager->getConnection()
+ ->query($requestCountQuery);
+
+ $offset = $queryCountResult['hits']['total'];
+ if (!$offset) {
+ return false;
+ }
+
+ $from = ['gte' => $data - self::DELTA];
+ if ($upper !== null) {
+ $to = ['lt' => $data - self::DELTA];
+ }
+
+ $requestQuery = $requestCountQuery;
+
+ $requestCountQuery['body']['query']['bool']['filter']['bool']['must']['range'] =
+ [$this->fieldName => array_merge($from, $to)];
+ $requestCountQuery['body']['from'] = $offset - 1;
+ $requestCountQuery['body']['size'] = $rightIndex - $offset + 1;
+ $queryResult = $this->connectionManager->getConnection()
+ ->query($requestQuery);
+
+ return array_reverse($this->arrayValuesToFloat($queryResult['hits']['hits'], $this->fieldName));
+ }
+
+ /**
+ * Conver array values to float type.
+ *
+ * @param array $hits
+ * @param string $fieldName
+ *
+ * @return float[]
+ */
+ private function arrayValuesToFloat(array $hits, string $fieldName): array
+ {
+ $returnPrices = [];
+ foreach ($hits as $hit) {
+ $returnPrices[] = (float)$hit['fields'][$fieldName][0];
+ }
+
+ return $returnPrices;
+ }
+
+ /**
+ * Prepare base query for search.
+ *
+ * @param array|null $from
+ * @param array|null $to
+ * @return array
+ */
+ private function prepareBaseRequestQuery($from = null, $to = null): array
+ {
+ $requestQuery = [
+ 'index' => $this->searchIndexNameResolver->getIndexName($this->storeId, Fulltext::INDEXER_ID),
+ 'type' => $this->clientConfig->getEntityType(),
+ 'body' => [
+ 'stored_fields' => [
+ '_id',
+ ],
+ 'query' => [
+ 'bool' => [
+ 'must' => [
+ 'match_all' => new \stdClass(),
+ ],
+ 'filter' => [
+ 'bool' => [
+ 'must' => [
+ [
+ 'terms' => [
+ '_id' => $this->entityIds,
+ ],
+ ],
+ [
+ 'range' => [
+ $this->fieldName => array_merge($from, $to),
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'sort' => [
+ $this->fieldName,
+ ],
+ ],
+ ];
+
+ return $requestQuery;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php
index 415c8b61ac6c..026d385da34d 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php
@@ -343,7 +343,7 @@ public function testAddFieldsMapping()
'product' => [
'_all' => [
'enabled' => true,
- 'type' => 'text'
+ 'type' => 'text',
],
'properties' => [
'name' => [
@@ -356,7 +356,8 @@ public function testAddFieldsMapping()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float'
+ 'type' => 'float',
+ 'store' => true,
],
],
],
@@ -366,7 +367,7 @@ public function testAddFieldsMapping()
'match_mapping_type' => 'string',
'mapping' => [
'type' => 'text',
- 'index' => 'no'
+ 'index' => 'no',
],
],
],
@@ -375,7 +376,7 @@ public function testAddFieldsMapping()
'match' => 'position_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'int'
+ 'type' => 'int',
],
],
],
@@ -409,7 +410,7 @@ public function testAddFieldsMappingFailure()
'product' => [
'_all' => [
'enabled' => true,
- 'type' => 'text'
+ 'type' => 'text',
],
'properties' => [
'name' => [
@@ -422,7 +423,8 @@ public function testAddFieldsMappingFailure()
'match' => 'price_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'float'
+ 'type' => 'float',
+ 'store' => true,
],
],
],
@@ -441,7 +443,7 @@ public function testAddFieldsMappingFailure()
'match' => 'position_*',
'match_mapping_type' => 'string',
'mapping' => [
- 'type' => 'int'
+ 'type' => 'int',
],
],
],
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/SearchAdapter/Aggregation/IntervalTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/SearchAdapter/Aggregation/IntervalTest.php
new file mode 100644
index 000000000000..4030d2dfaf0c
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/SearchAdapter/Aggregation/IntervalTest.php
@@ -0,0 +1,323 @@
+connectionManager = $this->getMockBuilder(ConnectionManager::class)
+ ->setMethods(['getConnection'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->fieldMapper = $this->getMockBuilder(FieldMapperInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->clientConfig = $this->getMockBuilder(Config::class)
+ ->setMethods([
+ 'getIndexName',
+ 'getEntityType',
+ ])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->customerSession = $this->getMockBuilder(CustomerSession::class)
+ ->setMethods(['getCustomerGroupId'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->customerSession->expects($this->any())
+ ->method('getCustomerGroupId')
+ ->willReturn(1);
+ $this->storeMock = $this->getMockBuilder(StoreInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->searchIndexNameResolver = $this
+ ->getMockBuilder(SearchIndexNameResolver::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->storeMock->expects($this->any())
+ ->method('getWebsiteId')
+ ->willReturn(1);
+ $this->storeMock->expects($this->any())
+ ->method('getId')
+ ->willReturn(1);
+ $this->clientConfig->expects($this->any())
+ ->method('getIndexName')
+ ->willReturn('indexName');
+ $this->clientConfig->expects($this->any())
+ ->method('getEntityType')
+ ->willReturn('product');
+ $this->clientMock = $this->getMockBuilder(ElasticsearchClient::class)
+ ->setMethods(['query'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->connectionManager->expects($this->any())
+ ->method('getConnection')
+ ->willReturn($this->clientMock);
+
+ $objectManagerHelper = new ObjectManagerHelper($this);
+ $this->model = $objectManagerHelper->getObject(
+ Interval::class,
+ [
+ 'connectionManager' => $this->connectionManager,
+ 'fieldMapper' => $this->fieldMapper,
+ 'clientConfig' => $this->clientConfig,
+ 'searchIndexNameResolver' => $this->searchIndexNameResolver,
+ 'fieldName' => 'price_0_1',
+ 'storeId' => 1,
+ 'entityIds' => [265, 313, 281],
+ ]
+ );
+ }
+
+ /**
+ * @dataProvider loadParamsProvider
+ * @param string $limit
+ * @param string $offset
+ * @param string $lower
+ * @param string $upper
+ * @param array $queryResult
+ * @param array $expected
+ * @return void
+ */
+ public function testLoad(
+ string $limit,
+ string $offset,
+ string $lower,
+ string $upper,
+ array $queryResult,
+ array $expected
+ ): void {
+ $this->processQuery($queryResult);
+
+ $this->assertEquals(
+ $expected,
+ $this->model->load($limit, $offset, $lower, $upper)
+ );
+ }
+
+ /**
+ * @dataProvider loadPrevParamsProvider
+ * @param string $data
+ * @param string $index
+ * @param string $lower
+ * @param array $queryResult
+ * @param array|bool $expected
+ * @return void
+ */
+ public function testLoadPrev(string $data, string $index, string $lower, array $queryResult, $expected): void
+ {
+ $this->processQuery($queryResult);
+
+ $this->assertEquals(
+ $expected,
+ $this->model->loadPrevious($data, $index, $lower)
+ );
+ }
+
+ /**
+ * @dataProvider loadNextParamsProvider
+ * @param string $data
+ * @param string $rightIndex
+ * @param string $upper
+ * @param array $queryResult
+ * @param array|bool $expected
+ * @return void
+ */
+ public function testLoadNext(string $data, string $rightIndex, string $upper, array $queryResult, $expected): void
+ {
+ $this->processQuery($queryResult);
+
+ $this->assertEquals(
+ $expected,
+ $this->model->loadNext($data, $rightIndex, $upper)
+ );
+ }
+
+ /**
+ * @param array $queryResult
+ * @return void
+ */
+ private function processQuery(array $queryResult): void
+ {
+ $this->searchIndexNameResolver->expects($this->any())
+ ->method('getIndexName')
+ ->willReturn('magento2_product_1');
+ $this->clientConfig->expects($this->any())
+ ->method('getEntityType')
+ ->willReturn('document');
+ $this->clientMock->expects($this->any())
+ ->method('query')
+ ->willReturn($queryResult);
+ }
+
+ /**
+ * @return array
+ */
+ public function loadParamsProvider(): array
+ {
+ return [
+ [
+ 'limit' => '6',
+ 'offset' => '2',
+ 'lower' => '24',
+ 'upper' => '42',
+ 'queryResult' => [
+ 'hits' => [
+ 'hits' => [
+ [
+ 'fields' => [
+ 'price_0_1' => [25],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'expected' => [25],
+ ],
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function loadPrevParamsProvider(): array
+ {
+ return [
+ [
+ 'data' => '24',
+ 'rightIndex' => '1',
+ 'upper' => '24',
+ 'queryResult' => [
+ 'hits' => [
+ 'total'=> '1',
+ 'hits' => [
+ [
+ 'fields' => [
+ 'price_0_1' => ['25'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'expected' => ['25.0'],
+ ],
+ [
+ 'data' => '24',
+ 'rightIndex' => '1',
+ 'upper' => '24',
+ 'queryResult' => [
+ 'hits' => ['total'=> '0'],
+ ],
+ 'expected' => false,
+ ],
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function loadNextParamsProvider(): array
+ {
+ return [
+ [
+ 'data' => '24',
+ 'rightIndex' => '2',
+ 'upper' => '42',
+ 'queryResult' => [
+ 'hits' => [
+ 'total'=> '1',
+ 'hits' => [
+ [
+ 'fields' => [
+ 'price_0_1' => ['25'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'expected' => ['25.0'],
+ ],
+ [
+ 'data' => '24',
+ 'rightIndex' => '2',
+ 'upper' => '42',
+ 'queryResult' => [
+ 'hits' => ['total'=> '0'],
+ ],
+ 'expected' => false,
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml
index 2d569eecfee5..0cfaba845fd6 100644
--- a/app/code/Magento/Elasticsearch/etc/di.xml
+++ b/app/code/Magento/Elasticsearch/etc/di.xml
@@ -166,7 +166,7 @@
- Magento\Elasticsearch\SearchAdapter\Aggregation\Interval
- - Magento\Elasticsearch\SearchAdapter\Aggregation\Interval
+ - Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval
diff --git a/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php
new file mode 100644
index 000000000000..aae30026b2b7
--- /dev/null
+++ b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php
@@ -0,0 +1,114 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->structure = $structure;
+ $this->encryptor = $encryptor;
+ $this->state = $state;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apply()
+ {
+ $this->moduleDataSetup->startSetup();
+
+ $this->reEncryptSystemConfigurationValues();
+
+ $this->moduleDataSetup->endSetup();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getDependencies()
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+
+ private function reEncryptSystemConfigurationValues()
+ {
+ $structure = $this->structure;
+ $paths = $this->state->emulateAreaCode(
+ \Magento\Framework\App\Area::AREA_ADMINHTML,
+ function () use ($structure) {
+ return $structure->getFieldPathsByAttribute(
+ 'backend_model',
+ \Magento\Config\Model\Config\Backend\Encrypted::class
+ );
+ }
+ );
+ // walk through found data and re-encrypt it
+ if ($paths) {
+ $table = $this->moduleDataSetup->getTable('core_config_data');
+ $values = $this->moduleDataSetup->getConnection()->fetchPairs(
+ $this->moduleDataSetup->getConnection()
+ ->select()
+ ->from($table, ['config_id', 'value'])
+ ->where('path IN (?)', $paths)
+ ->where('value NOT LIKE ?', '')
+ );
+ foreach ($values as $configId => $value) {
+ $this->moduleDataSetup->getConnection()->update(
+ $table,
+ ['value' => $this->encryptor->encrypt($this->encryptor->decrypt($value))],
+ ['config_id = ?' => (int)$configId]
+ );
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
index e7883693fbe7..e965e8ad207f 100644
--- a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
+++ b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
@@ -391,6 +391,7 @@ protected function _saveValidatedBunches()
$nextRowBackup = [];
$maxDataSize = $this->_resourceHelper->getMaxDataSize();
$bunchSize = $this->_importExportData->getBunchSize();
+ $skuSet = [];
$source->rewind();
$this->_dataSourceModel->cleanBunches();
@@ -407,6 +408,7 @@ protected function _saveValidatedBunches()
if ($source->valid()) {
try {
$rowData = $source->current();
+ $skuSet[$rowData['sku']] = true;
} catch (\InvalidArgumentException $e) {
$this->addRowError($e->getMessage(), $this->_processedRowsCount);
$this->_processedRowsCount++;
@@ -434,6 +436,8 @@ protected function _saveValidatedBunches()
$source->next();
}
}
+ $this->_processedEntitiesCount = count($skuSet);
+
return $this;
}
diff --git a/app/code/Magento/Sales/Console/Command/EncryptionPaymentDataUpdateCommand.php b/app/code/Magento/Sales/Console/Command/EncryptionPaymentDataUpdateCommand.php
new file mode 100644
index 000000000000..47ef24c542c2
--- /dev/null
+++ b/app/code/Magento/Sales/Console/Command/EncryptionPaymentDataUpdateCommand.php
@@ -0,0 +1,66 @@
+paymentResource = $paymentResource;
+ parent::__construct();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName(self::NAME)
+ ->setDescription(
+ 'Re-encrypts encrypted credit card data with latest encryption cipher.'
+ );
+ parent::configure();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ try {
+ $this->paymentResource->reEncryptCreditCardNumbers();
+ } catch (\Exception $e) {
+ $output->writeln('' . $e->getMessage() . '');
+ return Cli::RETURN_FAILURE;
+ }
+
+ return Cli::RETURN_SUCCESS;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdate.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdate.php
new file mode 100644
index 000000000000..4cbcfe49f4cc
--- /dev/null
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdate.php
@@ -0,0 +1,65 @@
+paymentResource = $paymentResource;
+ $this->encryptor = $encryptor;
+ }
+
+ /**
+ * Fetch encrypted credit card numbers using legacy ciphers and re-encrypt with latest cipher
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function reEncryptCreditCardNumbers()
+ {
+ $connection = $this->paymentResource->getConnection();
+ $table = $this->paymentResource->getMainTable();
+ $select = $connection->select()->from($table, ['entity_id', 'cc_number_enc'])
+ ->where(
+ 'cc_number_enc REGEXP ?',
+ sprintf(self::LEGACY_PATTERN, \Magento\Framework\Encryption\Encryptor::CIPHER_LATEST)
+ )->limit(1000);
+
+ while ($attributeValues = $connection->fetchPairs($select)) {
+ // save new values
+ foreach ($attributeValues as $valueId => $value) {
+ $connection->update(
+ $table,
+ ['cc_number_enc' => $this->encryptor->encrypt($this->encryptor->decrypt($value))],
+ ['entity_id = ?' => (int)$valueId, 'cc_number_enc = ?' => (string)$value]
+ );
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index 0f69389c2969..ed9e5240576d 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -980,6 +980,13 @@
+
+
+
+ - Magento\Sales\Console\Command\EncryptionPaymentDataUpdateCommand
+
+
+
diff --git a/composer.json b/composer.json
index b7435e98695c..0cf49466f870 100644
--- a/composer.json
+++ b/composer.json
@@ -41,6 +41,7 @@
"magento/zendframework1": "~1.14.1",
"monolog/monolog": "^1.17",
"oyejorge/less.php": "~1.7.0",
+ "paragonie/sodium_compat": "^1.6",
"pelago/emogrifier": "^2.0.0",
"php-amqplib/php-amqplib": "~2.7.0",
"phpseclib/mcrypt_compat": "1.0.5",
diff --git a/composer.lock b/composer.lock
index d90750e2547e..014c8eca5570 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "74013a4027763e05b29b127b4c03c752",
+ "content-hash": "da340950d3c725fcf2e01e73c48683d4",
"packages": [
{
"name": "braintree/braintree_php",
@@ -257,16 +257,16 @@
},
{
"name": "composer/composer",
- "version": "1.7.1",
+ "version": "1.7.2",
"source": {
"type": "git",
"url": "https://github.com/composer/composer.git",
- "reference": "5d9311d4555787c8a57fea15f82471499aedf712"
+ "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/composer/zipball/5d9311d4555787c8a57fea15f82471499aedf712",
- "reference": "5d9311d4555787c8a57fea15f82471499aedf712",
+ "url": "https://api.github.com/repos/composer/composer/zipball/576aab9b5abb2ed11a1c52353a759363216a4ad2",
+ "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2",
"shasum": ""
},
"require": {
@@ -333,7 +333,7 @@
"dependency",
"package"
],
- "time": "2018-08-07T07:39:23+00:00"
+ "time": "2018-08-16T14:57:12+00:00"
},
{
"name": "composer/semver",
@@ -460,16 +460,16 @@
},
{
"name": "composer/xdebug-handler",
- "version": "1.1.0",
+ "version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
- "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08"
+ "reference": "e1809da56ce1bd1b547a752936817341ac244d8e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08",
- "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e1809da56ce1bd1b547a752936817341ac244d8e",
+ "reference": "e1809da56ce1bd1b547a752936817341ac244d8e",
"shasum": ""
},
"require": {
@@ -500,7 +500,7 @@
"Xdebug",
"performance"
],
- "time": "2018-04-11T15:42:36+00:00"
+ "time": "2018-08-16T10:54:23+00:00"
},
{
"name": "container-interop/container-interop",
@@ -1106,6 +1106,88 @@
],
"time": "2018-07-04T16:31:37+00:00"
},
+ {
+ "name": "paragonie/sodium_compat",
+ "version": "v1.6.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/sodium_compat.git",
+ "reference": "7d0549c3947eaea620f4e523f42ab236cf7fd304"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/7d0549c3947eaea620f4e523f42ab236cf7fd304",
+ "reference": "7d0549c3947eaea620f4e523f42ab236cf7fd304",
+ "shasum": ""
+ },
+ "require": {
+ "paragonie/random_compat": ">=1",
+ "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^3|^4|^5"
+ },
+ "suggest": {
+ "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
+ "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "autoload.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com"
+ },
+ {
+ "name": "Frank Denis",
+ "email": "jedisct1@pureftpd.org"
+ }
+ ],
+ "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
+ "keywords": [
+ "Authentication",
+ "BLAKE2b",
+ "ChaCha20",
+ "ChaCha20-Poly1305",
+ "Chapoly",
+ "Curve25519",
+ "Ed25519",
+ "EdDSA",
+ "Edwards-curve Digital Signature Algorithm",
+ "Elliptic Curve Diffie-Hellman",
+ "Poly1305",
+ "Pure-PHP cryptography",
+ "RFC 7748",
+ "RFC 8032",
+ "Salpoly",
+ "Salsa20",
+ "X25519",
+ "XChaCha20-Poly1305",
+ "XSalsa20-Poly1305",
+ "Xchacha20",
+ "Xsalsa20",
+ "aead",
+ "cryptography",
+ "ecdh",
+ "elliptic curve",
+ "elliptic curve cryptography",
+ "encryption",
+ "libsodium",
+ "php",
+ "public-key cryptography",
+ "secret-key cryptography",
+ "side-channel resistant"
+ ],
+ "time": "2018-06-06T17:30:29+00:00"
+ },
{
"name": "pelago/emogrifier",
"version": "v2.0.0",
@@ -2400,16 +2482,16 @@
},
{
"name": "zendframework/zend-code",
- "version": "3.3.0",
+ "version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-code.git",
- "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d"
+ "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zendframework/zend-code/zipball/6b1059db5b368db769e4392c6cb6cc139e56640d",
- "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d",
+ "url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb",
+ "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb",
"shasum": ""
},
"require": {
@@ -2430,8 +2512,8 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev",
- "dev-develop": "3.3-dev"
+ "dev-master": "3.3.x-dev",
+ "dev-develop": "3.4.x-dev"
}
},
"autoload": {
@@ -2449,7 +2531,7 @@
"code",
"zf2"
],
- "time": "2017-10-20T15:21:32+00:00"
+ "time": "2018-08-13T20:36:59+00:00"
},
{
"name": "zendframework/zend-config",
@@ -4725,16 +4807,16 @@
},
{
"name": "consolidation/annotated-command",
- "version": "2.8.4",
+ "version": "2.8.5",
"source": {
"type": "git",
"url": "https://github.com/consolidation/annotated-command.git",
- "reference": "651541a0b68318a2a202bda558a676e5ad92223c"
+ "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/651541a0b68318a2a202bda558a676e5ad92223c",
- "reference": "651541a0b68318a2a202bda558a676e5ad92223c",
+ "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/1e8ff512072422b850b44aa721b5b303e4a5ebb3",
+ "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3",
"shasum": ""
},
"require": {
@@ -4773,7 +4855,7 @@
}
],
"description": "Initialize Symfony Console commands from annotated command class methods.",
- "time": "2018-05-25T18:04:25+00:00"
+ "time": "2018-08-18T23:51:49+00:00"
},
{
"name": "consolidation/config",
@@ -4935,16 +5017,16 @@
},
{
"name": "consolidation/robo",
- "version": "1.3.0",
+ "version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/consolidation/Robo.git",
- "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f"
+ "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/Robo/zipball/ac563abfadf7cb7314b4e152f2b5033a6c255f6f",
- "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f",
+ "url": "https://api.github.com/repos/consolidation/Robo/zipball/31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d",
+ "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d",
"shasum": ""
},
"require": {
@@ -4952,6 +5034,8 @@
"consolidation/config": "^1.0.10",
"consolidation/log": "~1",
"consolidation/output-formatters": "^3.1.13",
+ "consolidation/self-update": "^1",
+ "g1a/composer-test-scenarios": "^2",
"grasmash/yaml-expander": "^1.3",
"league/container": "^2.2",
"php": ">=5.5.0",
@@ -4968,7 +5052,6 @@
"codeception/aspect-mock": "^1|^2.1.1",
"codeception/base": "^2.3.7",
"codeception/verify": "^0.3.2",
- "g1a/composer-test-scenarios": "^2",
"goaop/framework": "~2.1.2",
"goaop/parser-reflection": "^1.1.0",
"natxet/cssmin": "3.0.4",
@@ -5011,7 +5094,50 @@
}
],
"description": "Modern task runner",
- "time": "2018-05-27T01:42:53+00:00"
+ "time": "2018-08-17T18:44:18+00:00"
+ },
+ {
+ "name": "consolidation/self-update",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/consolidation/self-update.git",
+ "reference": "adbb784e58cc0836d8522851f7e38ee7ade0d553"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/consolidation/self-update/zipball/adbb784e58cc0836d8522851f7e38ee7ade0d553",
+ "reference": "adbb784e58cc0836d8522851f7e38ee7ade0d553",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0",
+ "symfony/console": "^2.8|^3|^4",
+ "symfony/filesystem": "^2.5|^3|^4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SelfUpdate\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alexander Menk",
+ "email": "menk@mestrona.net"
+ }
+ ],
+ "description": "Provides a self:update command for Symfony Console applications.",
+ "time": "2018-08-17T04:50:59+00:00"
},
{
"name": "dflydev/dot-access-data",
@@ -5464,21 +5590,21 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v2.12.2",
+ "version": "v2.12.3",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
- "reference": "dcc87d5414e9d0bd316fce81a5bedb9ce720b183"
+ "reference": "b23d49981cfc95497d03081aeb6df6575196a0d3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/dcc87d5414e9d0bd316fce81a5bedb9ce720b183",
- "reference": "dcc87d5414e9d0bd316fce81a5bedb9ce720b183",
+ "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b23d49981cfc95497d03081aeb6df6575196a0d3",
+ "reference": "b23d49981cfc95497d03081aeb6df6575196a0d3",
"shasum": ""
},
"require": {
"composer/semver": "^1.4",
- "composer/xdebug-handler": "^1.0",
+ "composer/xdebug-handler": "^1.2",
"doctrine/annotations": "^1.2",
"ext-json": "*",
"ext-tokenizer": "*",
@@ -5551,7 +5677,7 @@
}
],
"description": "A tool to automatically fix PHP code style",
- "time": "2018-07-06T10:37:40+00:00"
+ "time": "2018-08-19T22:33:38+00:00"
},
{
"name": "fzaninotto/faker",
@@ -5603,6 +5729,39 @@
],
"time": "2018-07-12T10:23:15+00:00"
},
+ {
+ "name": "g1a/composer-test-scenarios",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/g1a/composer-test-scenarios.git",
+ "reference": "a166fd15191aceab89f30c097e694b7cf3db4880"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/a166fd15191aceab89f30c097e694b7cf3db4880",
+ "reference": "a166fd15191aceab89f30c097e694b7cf3db4880",
+ "shasum": ""
+ },
+ "bin": [
+ "scripts/create-scenario",
+ "scripts/dependency-licenses",
+ "scripts/install-scenario"
+ ],
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Greg Anderson",
+ "email": "greg.1.anderson@greenknowe.org"
+ }
+ ],
+ "description": "Useful scripts for testing multiple sets of Composer dependencies.",
+ "time": "2018-08-08T23:37:23+00:00"
+ },
{
"name": "grasmash/expander",
"version": "1.0.0",
diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php
index 8320514520b0..b238302e8f47 100644
--- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php
+++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php
@@ -17,7 +17,7 @@ class AssertDeviceDataIsPresentInBraintreeRequest extends AbstractConstraint
/**
* Log file name.
*/
- const FILE_NAME = 'debug.log';
+ const FILE_NAME = 'payment.log';
/**
* Device data pattern for regular expression.
diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml
index abfc9e09cb43..7d4811c4cb55 100644
--- a/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml
+++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml
@@ -40,6 +40,7 @@
- Stores
- System
- Global Search
+ - Catalog
diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml
index cae8dec6edcf..11a8ed006af3 100644
--- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml
@@ -14,7 +14,6 @@
- MAGETWO-66745: Magento\Integration\Test\TestCase\ActivateIntegrationEntityTest with ActivateIntegrationEntityTestVariation1 fails randomly
diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php
index c00651638627..803bee65fcdf 100644
--- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php
+++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php
@@ -16,6 +16,11 @@
*/
abstract class AbstractAssertWishlistProductDetails extends AbstractAssertForm
{
+ /**
+ * @inheritdoc
+ */
+ protected $skippedFields = ['sku'];
+
/**
* Assert product details.
*
diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml
index fd80d385e485..95e6a854ed26 100644
--- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml
@@ -37,7 +37,6 @@
- stable:no
bundleProduct::bundle_dynamic_product
@@ -45,7 +44,6 @@
- stable:no
bundleProduct::bundle_fixed_product
diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php
index c199214f6857..496d03dbd7c5 100644
--- a/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php
+++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php
@@ -4,6 +4,8 @@
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\EncryptionKey\Model\ResourceModel\Key;
class ChangeTest extends \PHPUnit\Framework\TestCase
@@ -79,7 +81,7 @@ public function testChangeEncryptionKey()
)
);
$this->assertNotContains($testValue, $values1);
- $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9+/]+=)|', current($values1));
+ $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9+/]+=*)|', current($values1));
// Verify that the credit card number has been encrypted
$values2 = $connection->fetchPairs(
@@ -89,7 +91,7 @@ public function testChangeEncryptionKey()
)
);
$this->assertNotContains('1111111111', $values2);
- $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9+/]+=)|', current($values1));
+ $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9+/]+=*)|', current($values2));
/** clean up */
$select = $connection->select()->from($configModel->getMainTable())->where('path=?', $testPath);
diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php
new file mode 100644
index 000000000000..3a47692bdb93
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php
@@ -0,0 +1,98 @@
+objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ $this->deployConfig = $this->objectManager->get(DeploymentConfig::class);
+ }
+
+ public function testChangeEncryptionKey()
+ {
+ $testPath = 'test/config';
+ $testValue = 'test';
+
+ $structureMock = $this->createMock(\Magento\Config\Model\Config\Structure\Proxy::class);
+ $structureMock->expects($this->once())
+ ->method('getFieldPathsByAttribute')
+ ->will($this->returnValue([$testPath]));
+
+ /** @var \Magento\Config\Model\ResourceModel\Config $configModel */
+ $configModel = $this->objectManager->create(\Magento\Config\Model\ResourceModel\Config::class);
+ $configModel->saveConfig($testPath, $this->legacyEncrypt($testValue), 'default', 0);
+
+ /** @var \Magento\EncryptionKey\Setup\Patch\Data\SodiumChachaPatch $patch */
+ $patch = $this->objectManager->create(
+ \Magento\EncryptionKey\Setup\Patch\Data\SodiumChachaPatch::class,
+ [
+ 'structure' => $structureMock,
+ ]
+ );
+ $patch->apply();
+
+ $connection = $configModel->getConnection();
+ $values = $connection->fetchPairs(
+ $connection->select()->from(
+ $configModel->getMainTable(),
+ ['config_id', 'value']
+ )->where(
+ 'path IN (?)',
+ [$testPath]
+ )->where(
+ 'value NOT LIKE ?',
+ ''
+ )
+ );
+
+ /** @var \Magento\Framework\Encryption\EncryptorInterface $encyptor */
+ $encyptor = $this->objectManager->get(\Magento\Framework\Encryption\EncryptorInterface::class);
+
+ $rawConfigValue = array_pop($values);
+
+ $this->assertNotEquals($testValue, $rawConfigValue);
+ $this->assertStringStartsWith('0:' . Encryptor::CIPHER_LATEST . ':', $rawConfigValue);
+ $this->assertEquals($testValue, $encyptor->decrypt($rawConfigValue));
+ }
+
+ private function legacyEncrypt(string $data): string
+ {
+ // @codingStandardsIgnoreStart
+ $handle = @mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
+ $initVectorSize = @mcrypt_enc_get_iv_size($handle);
+ $initVector = str_repeat("\0", $initVectorSize);
+ @mcrypt_generic_init($handle, $this->deployConfig->get(static::PATH_KEY), $initVector);
+
+ $encrpted = @mcrypt_generic($handle, $data);
+
+ @mcrypt_generic_deinit($handle);
+ @mcrypt_module_close($handle);
+ // @codingStandardsIgnoreEnd
+
+ return '0:' . Encryptor::CIPHER_RIJNDAEL_256 . ':' . base64_encode($encrpted);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php b/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php
index f5b48e79b986..12f398b705b2 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Framework\Encryption;
class ModelTest extends \PHPUnit\Framework\TestCase
@@ -40,7 +42,16 @@ public function testEncryptDecrypt2()
public function testValidateKey()
{
$validKey = md5(uniqid());
- $this->assertInstanceOf(\Magento\Framework\Encryption\Crypt::class, $this->_model->validateKey($validKey));
+ $this->_model->validateKey($validKey);
+ }
+
+ /**
+ * @expectedException \Exception
+ */
+ public function testValidateKeyInvalid()
+ {
+ $invalidKey = '---- ';
+ $this->_model->validateKey($invalidKey);
}
public function testGetValidateHash()
diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EntityAbstractTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EntityAbstractTest.php
new file mode 100644
index 000000000000..db4d9c546864
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EntityAbstractTest.php
@@ -0,0 +1,68 @@
+create(\Magento\Framework\Filesystem::class);
+ $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+ $source = new Csv(__DIR__ . '/_files/advanced_price_for_validation_test.csv', $directory);
+ $source->rewind();
+
+ $eavConfig = $this->createMock(\Magento\Eav\Model\Config::class);
+ $entityTypeMock = $this->createMock(\Magento\Eav\Model\Entity\Type::class);
+ $eavConfig->expects($this->any())->method('getEntityType')->willReturn($entityTypeMock);
+
+ /** @var $model AbstractEntity|\PHPUnit_Framework_MockObject_MockObject */
+ $model = $this->getMockForAbstractClass(
+ AbstractEntity::class,
+ [
+ $objectManager->get(\Magento\Framework\Json\Helper\Data::class),
+ $objectManager->get(\Magento\ImportExport\Helper\Data::class),
+ $objectManager->get(\Magento\ImportExport\Model\ResourceModel\Import\Data::class),
+ $eavConfig,
+ $objectManager->get(\Magento\Framework\App\ResourceConnection::class),
+ $objectManager->get(\Magento\ImportExport\Model\ResourceModel\Helper::class),
+ $objectManager->get(\Magento\Framework\Stdlib\StringUtils::class),
+ $objectManager->get(ProcessingErrorAggregatorInterface::class),
+ ],
+ '',
+ true,
+ false,
+ true,
+ ['validateRow', 'getEntityTypeCode']
+ );
+ $model->expects($this->any())->method('validateRow')->willReturn(true);
+ $model->expects($this->any())->method('getEntityTypeCode')->willReturn('catalog_product');
+
+ $model->setSource($source);
+
+ $method = new \ReflectionMethod($model, '_saveValidatedBunches');
+ $method->setAccessible(true);
+ $method->invoke($model);
+
+ $this->assertEquals(1, $model->getProcessedEntitiesCount());
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/_files/advanced_price_for_validation_test.csv b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/_files/advanced_price_for_validation_test.csv
new file mode 100644
index 000000000000..fb42afb90188
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/_files/advanced_price_for_validation_test.csv
@@ -0,0 +1,3 @@
+sku,tier_price_website,tier_price_customer_group,tier_price_qty,tier_price,tier_price_value_type
+SimpleProduct,All Websites [USD],ALL GROUPS,100,50,Fixed
+SimpleProduct,All Websites [USD],ALL GROUPS,33,55,Fixed
diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdateTest.php
new file mode 100644
index 000000000000..c0de639ea742
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdateTest.php
@@ -0,0 +1,50 @@
+get(\Magento\Framework\Encryption\EncryptorInterface::class);
+
+ /** @var \Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdate $resource */
+ $resource = $objectManager->create(\Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdate::class);
+ $resource->reEncryptCreditCardNumbers();
+
+ /** @var \Magento\Sales\Model\ResourceModel\Order\Payment\Collection $collection */
+ $collection = $objectManager->create(\Magento\Sales\Model\ResourceModel\Order\Payment\Collection::class);
+ $collection->addFieldToFilter('cc_number_enc', ['notnull' => true]);
+
+ $this->assertGreaterThan(0, $collection->getTotalCount());
+
+ /** @var \Magento\Sales\Model\Order\Payment $payment */
+ foreach ($collection->getItems() as $payment) {
+ $this->assertEquals(
+ static::TEST_CC_NUMBER,
+ $encyptor->decrypt($payment->getCcNumberEnc())
+ );
+
+ $this->assertStringStartsWith('0:' . Encryptor::CIPHER_LATEST . ':', $payment->getCcNumberEnc());
+ }
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/payment_enc_cc.php b/dev/tests/integration/testsuite/Magento/Sales/_files/payment_enc_cc.php
new file mode 100644
index 000000000000..bfa643fcf5f9
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Sales/_files/payment_enc_cc.php
@@ -0,0 +1,53 @@
+get(DeploymentConfig::class);
+
+/**
+ * Creates an encrypted card number with the current crypt key using
+ * a legacy cipher.
+ */
+// @codingStandardsIgnoreStart
+$handle = @mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
+$initVectorSize = @mcrypt_enc_get_iv_size($handle);
+$initVector = str_repeat("\0", $initVectorSize);
+@mcrypt_generic_init($handle, $deployConfig->get('crypt/key'), $initVector);
+
+$encCcNumber = @mcrypt_generic($handle, EncryptionUpdateTest::TEST_CC_NUMBER);
+
+@mcrypt_generic_deinit($handle);
+@mcrypt_module_close($handle);
+// @codingStandardsIgnoreEnd
+
+/** @var SearchCriteria $searchCriteria */
+$searchCriteria = $objectManager->get(SearchCriteriaBuilder::class)
+ ->addFilter('increment_id', '100000001')
+ ->create();
+
+$orders = $orderRepository->getList($searchCriteria)->getItems();
+$order = array_pop($orders);
+
+/** @var \Magento\Sales\Model\ResourceModel\Order\Payment $resource */
+$resource = $objectManager->create(\Magento\Sales\Model\ResourceModel\Order\Payment::class);
+$resource->getConnection()->insert(
+ $resource->getMainTable(),
+ [
+ 'parent_id' => $order->getId(),
+ 'cc_number_enc' => '0:2:' . base64_encode($encCcNumber),
+ ]
+);
diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php
index 0f7231e19aee..62816cd4e4f7 100644
--- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php
+++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php
@@ -10,4 +10,6 @@
'dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php',
'lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php',
'dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php',
+ 'lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php',
+ 'lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php'
];
diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php
index 78ab26401b0f..12c10990a3af 100755
--- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php
+++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php
@@ -4237,4 +4237,5 @@
['Zend_Feed', 'Zend\Feed'],
['Zend_Uri', 'Zend\Uri\Uri'],
['Zend_Mime', 'Magento\Framework\HTTP\Mime'],
+ ['Magento\Framework\Encryption\Crypt', 'Magento\Framework\Encryption\EncryptionAdapterInterface'],
];
diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php
index 52e7137fa3dd..92f0302d93ba 100644
--- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php
+++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php
@@ -4,6 +4,8 @@
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Framework\Config;
/**
@@ -127,5 +129,5 @@ class ConfigOptionsListConstants
/**
* Size of random string generated for store's encryption key
*/
- const STORE_KEY_RANDOM_STRING_SIZE = 32;
+ const STORE_KEY_RANDOM_STRING_SIZE = SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES;
}
diff --git a/lib/internal/Magento/Framework/Encryption/Adapter/EncryptionAdapterInterface.php b/lib/internal/Magento/Framework/Encryption/Adapter/EncryptionAdapterInterface.php
new file mode 100644
index 000000000000..b9bbb089ae0c
--- /dev/null
+++ b/lib/internal/Magento/Framework/Encryption/Adapter/EncryptionAdapterInterface.php
@@ -0,0 +1,24 @@
+cipher = $cipher;
+ $this->mode = $mode;
+ // @codingStandardsIgnoreLine
+ $this->handle = @mcrypt_module_open($cipher, '', $mode, '');
+ try {
+ // @codingStandardsIgnoreLine
+ $maxKeySize = @mcrypt_enc_get_key_size($this->handle);
+ if (strlen($key) > $maxKeySize) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ new \Magento\Framework\Phrase('Key must not exceed %1 bytes.', [$maxKeySize])
+ );
+ }
+ // @codingStandardsIgnoreLine
+ $initVectorSize = @mcrypt_enc_get_iv_size($this->handle);
+ if (null === $initVector) {
+ /* Set vector to zero bytes to not use it */
+ $initVector = str_repeat("\0", $initVectorSize);
+ } elseif (!is_string($initVector) || strlen($initVector) != $initVectorSize) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ new \Magento\Framework\Phrase(
+ 'Init vector must be a string of %1 bytes.',
+ [$initVectorSize]
+ )
+ );
+ }
+ $this->initVector = $initVector;
+ } catch (\Exception $e) {
+ // @codingStandardsIgnoreLine
+ @mcrypt_module_close($this->handle);
+ throw new \Magento\Framework\Exception\LocalizedException(new \Magento\Framework\Phrase($e->getMessage()));
+ }
+ // @codingStandardsIgnoreLine
+ @mcrypt_generic_init($this->handle, $key, $initVector);
+ }
+
+ /**
+ * Destructor frees allocated resources
+ */
+ public function __destruct()
+ {
+ // @codingStandardsIgnoreStart
+ @mcrypt_generic_deinit($this->handle);
+ @mcrypt_module_close($this->handle);
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * Retrieve a name of currently used cryptographic algorithm
+ *
+ * @return string
+ */
+ public function getCipher(): string
+ {
+ return $this->cipher;
+ }
+
+ /**
+ * Mode in which cryptographic algorithm is running
+ *
+ * @return string
+ */
+ public function getMode(): string
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Retrieve an actual value of initial vector that has been used to initialize a cipher
+ *
+ * @return string
+ */
+ public function getInitVector(): ?string
+ {
+ return $this->initVector;
+ }
+
+ /**
+ * Get the current mcrypt handle
+ *
+ * @return resource
+ */
+ public function getHandle()
+ {
+ return $this->handle;
+ }
+
+ /**
+ * Encrypt a string
+ *
+ * @param string $data String to encrypt
+ * @return string
+ * @throws \Exception
+ */
+ public function encrypt(string $data): string
+ {
+ throw new \Exception(
+ (string)new \Magento\Framework\Phrase('Mcrypt cannot be used for encryption. Use Sodium instead')
+ );
+ }
+
+ /**
+ * Decrypt a string
+ *
+ * @param string $data
+ * @return string
+ */
+ public function decrypt(string $data): string
+ {
+ if (strlen($data) == 0) {
+ return $data;
+ }
+ // @codingStandardsIgnoreLine
+ $data = @mdecrypt_generic($this->handle, $data);
+ /*
+ * Returned string can in fact be longer than the unencrypted string due to the padding of the data
+ * @link http://www.php.net/manual/en/function.mdecrypt-generic.php
+ */
+ $data = rtrim($data, "\0");
+ return $data;
+ }
+}
diff --git a/lib/internal/Magento/Framework/Encryption/Adapter/SodiumChachaIetf.php b/lib/internal/Magento/Framework/Encryption/Adapter/SodiumChachaIetf.php
new file mode 100644
index 000000000000..9f9facf98ff8
--- /dev/null
+++ b/lib/internal/Magento/Framework/Encryption/Adapter/SodiumChachaIetf.php
@@ -0,0 +1,70 @@
+key = $key;
+ }
+
+ /**
+ * Encrypt a string
+ *
+ * @param string $data
+ * @return string string
+ */
+ public function encrypt(string $data): string
+ {
+ $nonce = random_bytes(SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES);
+ $cipherText = sodium_crypto_aead_chacha20poly1305_ietf_encrypt(
+ (string)$data,
+ $nonce,
+ $nonce,
+ $this->key
+ );
+
+ return $nonce . $cipherText;
+ }
+
+ /**
+ * Decrypt a string
+ *
+ * @param string $data
+ * @return string
+ */
+ public function decrypt(string $data): string
+ {
+ $nonce = mb_substr($data, 0, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, '8bit');
+ $payload = mb_substr($data, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, null, '8bit');
+
+ $plainText = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(
+ $payload,
+ $nonce,
+ $nonce,
+ $this->key
+ );
+
+ return $plainText;
+ }
+}
diff --git a/lib/internal/Magento/Framework/Encryption/Crypt.php b/lib/internal/Magento/Framework/Encryption/Crypt.php
index 5f368f0d5b79..d52db40b395a 100644
--- a/lib/internal/Magento/Framework/Encryption/Crypt.php
+++ b/lib/internal/Magento/Framework/Encryption/Crypt.php
@@ -4,12 +4,15 @@
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Framework\Encryption;
/**
* Class encapsulates cryptographic algorithm
*
* @api
+ * @deprecated
*/
class Crypt
{
@@ -29,11 +32,11 @@ class Crypt
protected $_initVector;
/**
- * Encryption algorithm module handle
+ * Mcrypt adapter
*
- * @var resource
+ * @var \Magento\Framework\Encryption\Adapter\Mcrypt
*/
- protected $_handle;
+ private $mcrypt;
/**
* Constructor
@@ -53,64 +56,30 @@ public function __construct(
$mode = MCRYPT_MODE_ECB,
$initVector = false
) {
- $this->_cipher = $cipher;
- $this->_mode = $mode;
- // @codingStandardsIgnoreStart
- $this->_handle = @mcrypt_module_open($cipher, '', $mode, '');
- // @codingStandardsIgnoreEnd
- try {
- // @codingStandardsIgnoreStart
- $maxKeySize = @mcrypt_enc_get_key_size($this->_handle);
- // @codingStandardsIgnoreEnd
- if (strlen($key) > $maxKeySize) {
- throw new \Magento\Framework\Exception\LocalizedException(
- new \Magento\Framework\Phrase('Key must not exceed %1 bytes.', [$maxKeySize])
- );
- }
+ if (true === $initVector) {
// @codingStandardsIgnoreStart
- $initVectorSize = @mcrypt_enc_get_iv_size($this->_handle);
+ $handle = @mcrypt_module_open($cipher, '', $mode, '');
+ $initVectorSize = @mcrypt_enc_get_iv_size($handle);
// @codingStandardsIgnoreEnd
- if (true === $initVector) {
- /* Generate a random vector from human-readable characters */
- $abc = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
- $initVector = '';
- for ($i = 0; $i < $initVectorSize; $i++) {
- $initVector .= $abc[random_int(0, strlen($abc) - 1)];
- }
- } elseif (false === $initVector) {
- /* Set vector to zero bytes to not use it */
- $initVector = str_repeat("\0", $initVectorSize);
- } elseif (!is_string($initVector) || strlen($initVector) != $initVectorSize) {
- throw new \Magento\Framework\Exception\LocalizedException(
- new \Magento\Framework\Phrase(
- 'Init vector must be a string of %1 bytes.',
- [$initVectorSize]
- )
- );
+
+ /* Generate a random vector from human-readable characters */
+ $allowedCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+ $initVector = '';
+ for ($i = 0; $i < $initVectorSize; $i++) {
+ $initVector .= $allowedCharacters[rand(0, strlen($allowedCharacters) - 1)];
}
- $this->_initVector = $initVector;
- } catch (\Exception $e) {
// @codingStandardsIgnoreStart
- @mcrypt_module_close($this->_handle);
+ @mcrypt_generic_deinit($handle);
+ @mcrypt_module_close($handle);
// @codingStandardsIgnoreEnd
- throw $e;
}
- // @codingStandardsIgnoreStart
- @mcrypt_generic_init($this->_handle, $key, $initVector);
- // @codingStandardsIgnoreEnd
- }
- /**
- * Destructor frees allocated resources
- */
- public function __destruct()
- {
- // @codingStandardsIgnoreStart
- @mcrypt_generic_deinit($this->_handle);
- // @codingStandardsIgnoreEnd
- // @codingStandardsIgnoreStart
- @mcrypt_module_close($this->_handle);
- // @codingStandardsIgnoreEnd
+ $this->mcrypt = new \Magento\Framework\Encryption\Adapter\Mcrypt(
+ $key,
+ $cipher,
+ $mode,
+ $initVector === false ? null : $initVector
+ );
}
/**
@@ -120,7 +89,7 @@ public function __destruct()
*/
public function getCipher()
{
- return $this->_cipher;
+ return $this->mcrypt->getCipher();
}
/**
@@ -130,7 +99,7 @@ public function getCipher()
*/
public function getMode()
{
- return $this->_mode;
+ return $this->mcrypt->getMode();
}
/**
@@ -140,7 +109,7 @@ public function getMode()
*/
public function getInitVector()
{
- return $this->_initVector;
+ return $this->mcrypt->getInitVector();
}
/**
@@ -154,9 +123,8 @@ public function encrypt($data)
if (strlen($data) == 0) {
return $data;
}
- // @codingStandardsIgnoreStart
- return @mcrypt_generic($this->_handle, $data);
- // @codingStandardsIgnoreEnd
+ // @codingStandardsIgnoreLine
+ return @mcrypt_generic($this->mcrypt->getHandle(), $data);
}
/**
@@ -167,17 +135,6 @@ public function encrypt($data)
*/
public function decrypt($data)
{
- if (strlen($data) == 0) {
- return $data;
- }
- // @codingStandardsIgnoreStart
- $data = @mdecrypt_generic($this->_handle, $data);
- // @codingStandardsIgnoreEnd
- /*
- * Returned string can in fact be longer than the unencrypted string due to the padding of the data
- * @link http://www.php.net/manual/en/function.mdecrypt-generic.php
- */
- $data = rtrim($data, "\0");
- return $data;
+ return $this->mcrypt->decrypt($data);
}
}
diff --git a/lib/internal/Magento/Framework/Encryption/Encryptor.php b/lib/internal/Magento/Framework/Encryption/Encryptor.php
index 881c5843b155..f7d52d5474ad 100644
--- a/lib/internal/Magento/Framework/Encryption/Encryptor.php
+++ b/lib/internal/Magento/Framework/Encryption/Encryptor.php
@@ -3,11 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Framework\Encryption;
use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\Encryption\Adapter\EncryptionAdapterInterface;
use Magento\Framework\Encryption\Helper\Security;
use Magento\Framework\Math\Random;
+use Magento\Framework\Encryption\Adapter\SodiumChachaIetf;
+use Magento\Framework\Encryption\Adapter\Mcrypt;
/**
* Class Encryptor provides basic logic for hashing strings and encrypting/decrypting misc data
@@ -56,7 +62,9 @@ class Encryptor implements EncryptorInterface
const CIPHER_RIJNDAEL_256 = 2;
- const CIPHER_LATEST = 2;
+ const CIPHER_AEAD_CHACHA20POLY1305 = 3;
+
+ const CIPHER_LATEST = 3;
/**#@-*/
/**
@@ -108,6 +116,7 @@ class Encryptor implements EncryptorInterface
private $random;
/**
+ * Encryptor constructor.
* @param Random $random
* @param DeploymentConfig $deploymentConfig
*/
@@ -118,7 +127,7 @@ public function __construct(
$this->random = $random;
// load all possible keys
- $this->keys = preg_split('/\s+/s', trim($deploymentConfig->get(self::PARAM_CRYPT_KEY)));
+ $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get(self::PARAM_CRYPT_KEY)));
$this->keyVersion = count($this->keys) - 1;
}
@@ -133,7 +142,12 @@ public function __construct(
*/
public function validateCipher($version)
{
- $types = [self::CIPHER_BLOWFISH, self::CIPHER_RIJNDAEL_128, self::CIPHER_RIJNDAEL_256];
+ $types = [
+ self::CIPHER_BLOWFISH,
+ self::CIPHER_RIJNDAEL_128,
+ self::CIPHER_RIJNDAEL_256,
+ self::CIPHER_AEAD_CHACHA20POLY1305,
+ ];
$version = (int)$version;
if (!in_array($version, $types, true)) {
@@ -172,7 +186,7 @@ public function getHash($password, $salt = false, $version = self::HASH_VERSION_
*/
public function hash($data, $version = self::HASH_VERSION_LATEST)
{
- return hash($this->hashVersionMap[$version], $data);
+ return hash($this->hashVersionMap[$version], (string)$data);
}
/**
@@ -260,14 +274,11 @@ private function getPasswordVersion()
*/
public function encrypt($data)
{
- $crypt = $this->getCrypt();
- if (null === $crypt) {
- return $data;
- }
- return $this->keyVersion . ':' . $this->cipher . ':' . (MCRYPT_MODE_CBC ===
- $crypt->getMode() ? $crypt->getInitVector() . ':' : '') . base64_encode(
- $crypt->encrypt((string)$data)
- );
+ $crypt = new SodiumChachaIetf($this->keys[$this->keyVersion]);
+
+ return $this->keyVersion .
+ ':' . self::CIPHER_AEAD_CHACHA20POLY1305 .
+ ':' . base64_encode($crypt->encrypt($data));
}
/**
@@ -279,6 +290,7 @@ public function encrypt($data)
*
* @param string $data
* @return string
+ * @throws \Exception
*/
public function decrypt($data)
{
@@ -286,11 +298,11 @@ public function decrypt($data)
$parts = explode(':', $data, 4);
$partsCount = count($parts);
- $initVector = false;
+ $initVector = null;
// specified key, specified crypt, specified iv
if (4 === $partsCount) {
list($keyVersion, $cryptVersion, $iv, $data) = $parts;
- $initVector = $iv ? $iv : false;
+ $initVector = $iv ? $iv : null;
$keyVersion = (int)$keyVersion;
$cryptVersion = self::CIPHER_RIJNDAEL_256;
// specified key, specified crypt
@@ -325,10 +337,9 @@ public function decrypt($data)
}
/**
- * Return crypt model, instantiate if it is empty
+ * Validate key contains only allowed characters
*
* @param string|null $key NULL value means usage of the default key specified on constructor
- * @return \Magento\Framework\Encryption\Crypt
* @throws \Exception
*/
public function validateKey($key)
@@ -336,7 +347,6 @@ public function validateKey($key)
if (preg_match('/\s/s', $key)) {
throw new \Exception((string)new \Magento\Framework\Phrase('The encryption key format is invalid.'));
}
- return $this->getCrypt($key);
}
/**
@@ -344,6 +354,7 @@ public function validateKey($key)
*
* @param string $key
* @return $this
+ * @throws \Exception
*/
public function setNewKey($key)
{
@@ -370,11 +381,15 @@ public function exportKeys()
*
* @param string $key
* @param int $cipherVersion
- * @param bool $initVector
- * @return Crypt|null
+ * @param string $initVector
+ * @return EncryptionAdapterInterface|null
+ * @throws \Exception
*/
- protected function getCrypt($key = null, $cipherVersion = null, $initVector = true)
- {
+ private function getCrypt(
+ string $key = null,
+ int $cipherVersion = null,
+ string $initVector = null
+ ): ?EncryptionAdapterInterface {
if (null === $key && null === $cipherVersion) {
$cipherVersion = self::CIPHER_RIJNDAEL_256;
}
@@ -392,6 +407,10 @@ protected function getCrypt($key = null, $cipherVersion = null, $initVector = tr
}
$cipherVersion = $this->validateCipher($cipherVersion);
+ if ($cipherVersion >= self::CIPHER_AEAD_CHACHA20POLY1305) {
+ return new SodiumChachaIetf($key);
+ }
+
if ($cipherVersion === self::CIPHER_RIJNDAEL_128) {
$cipher = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_ECB;
@@ -403,6 +422,6 @@ protected function getCrypt($key = null, $cipherVersion = null, $initVector = tr
$mode = MCRYPT_MODE_ECB;
}
- return new Crypt($key, $cipher, $mode, $initVector);
+ return new Mcrypt($key, $cipher, $mode, $initVector);
}
}
diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/McryptTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/McryptTest.php
new file mode 100644
index 000000000000..2622441aa089
--- /dev/null
+++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/McryptTest.php
@@ -0,0 +1,188 @@
+ [MCRYPT_MODE_ECB],
+ MCRYPT_RIJNDAEL_128 => [MCRYPT_MODE_ECB],
+ MCRYPT_RIJNDAEL_256 => [MCRYPT_MODE_CBC],
+ ];
+
+ protected function setUp()
+ {
+ $this->key = substr(__CLASS__, -32, 32);
+ }
+
+ protected function getRandomString(int $length): string
+ {
+ $result = '';
+
+ do {
+ $result .= sha1(microtime());
+ } while (strlen($result) < $length);
+
+ return substr($result, -$length);
+ }
+
+ private function requireCipherInfo()
+ {
+ $filename = __DIR__ . '/../Crypt/_files/_cipher_info.php';
+
+ if (!self::$cipherInfo) {
+ self::$cipherInfo = include $filename;
+ }
+ }
+
+ private function getKeySize(string $cipherName, string $modeName): int
+ {
+ $this->requireCipherInfo();
+ return self::$cipherInfo[$cipherName][$modeName]['key_size'];
+ }
+
+ private function getInitVectorSize(string $cipherName, string $modeName): int
+ {
+ $this->requireCipherInfo();
+ return self::$cipherInfo[$cipherName][$modeName]['iv_size'];
+ }
+
+ public function getCipherModeCombinations(): array
+ {
+ $result = [];
+ foreach (self::SUPPORTED_CIPHER_MODE_COMBINATIONS as $cipher => $modes) {
+ /** @var array $modes */
+ foreach ($modes as $mode) {
+ $result[$cipher . '-' . $mode] = [$cipher, $mode];
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * @dataProvider getCipherModeCombinations
+ */
+ public function testConstructor(string $cipher, string $mode)
+ {
+ /* Generate random init vector */
+ $initVector = $this->getRandomString($this->getInitVectorSize($cipher, $mode));
+
+ $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt($this->key, $cipher, $mode, $initVector);
+
+ $this->assertEquals($cipher, $crypt->getCipher());
+ $this->assertEquals($mode, $crypt->getMode());
+ $this->assertEquals($initVector, $crypt->getInitVector());
+ }
+
+ public function getConstructorExceptionData(): array
+ {
+ $key = substr(__CLASS__, -32, 32);
+ $result = [];
+ foreach (self::SUPPORTED_CIPHER_MODE_COMBINATIONS as $cipher => $modes) {
+ /** @var array $modes */
+ foreach ($modes as $mode) {
+ $tooLongKey = str_repeat('-', $this->getKeySize($cipher, $mode) + 1);
+ $tooShortInitVector = str_repeat('-', $this->getInitVectorSize($cipher, $mode) - 1);
+ $tooLongInitVector = str_repeat('-', $this->getInitVectorSize($cipher, $mode) + 1);
+ $result['tooLongKey-' . $cipher . '-' . $mode . '-false'] = [$tooLongKey, $cipher, $mode, false];
+ $keyPrefix = 'key-' . $cipher . '-' . $mode;
+ $result[$keyPrefix . '-tooShortInitVector'] = [$key, $cipher, $mode, $tooShortInitVector];
+ $result[$keyPrefix . '-tooLongInitVector'] = [$key, $cipher, $mode, $tooLongInitVector];
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * @dataProvider getConstructorExceptionData
+ * @expectedException \Magento\Framework\Exception\LocalizedException
+ */
+ public function testConstructorException(string $key, string $cipher, string $mode, ?string $initVector = null)
+ {
+ new \Magento\Framework\Encryption\Adapter\Mcrypt($key, $cipher, $mode, $initVector);
+ }
+
+ public function testConstructorDefaults()
+ {
+ $cryptExpected = new \Magento\Framework\Encryption\Adapter\Mcrypt(
+ $this->key,
+ MCRYPT_BLOWFISH,
+ MCRYPT_MODE_ECB,
+ null
+ );
+ $cryptActual = new \Magento\Framework\Encryption\Adapter\Mcrypt($this->key);
+
+ $this->assertEquals($cryptExpected->getCipher(), $cryptActual->getCipher());
+ $this->assertEquals($cryptExpected->getMode(), $cryptActual->getMode());
+ $this->assertEquals($cryptExpected->getInitVector(), $cryptActual->getInitVector());
+ }
+
+ public function getCryptData(): array
+ {
+ $fixturesFilename = __DIR__ . '/../Crypt/_files/_crypt_fixtures.php';
+
+ $result = include $fixturesFilename;
+ /* Restore encoded string back to binary */
+ foreach ($result as &$cryptParams) {
+ $cryptParams[5] = base64_decode($cryptParams[5]);
+ }
+ unset($cryptParams);
+
+ return $result;
+ }
+
+ /**
+ * @dataProvider getCryptData
+ */
+ public function testDecrypt(
+ string $key,
+ string $cipher,
+ string $mode,
+ ?string $initVector,
+ string $expectedData,
+ string $inputData
+ ) {
+ $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt($key, $cipher, $mode, $initVector);
+ $actualData = $crypt->decrypt($inputData);
+ $this->assertEquals($expectedData, $actualData);
+ }
+
+ /**
+ * @expectedException \Exception
+ */
+ public function testEncrypt()
+ {
+ $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt($this->key);
+ $crypt->encrypt('Hello World!!');
+ }
+
+ /**
+ * @dataProvider getCipherModeCombinations
+ */
+ public function testInitVectorNone(string $cipher, string $mode)
+ {
+ $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt(
+ $this->key,
+ $cipher,
+ $mode,
+ null
+ );
+ $actualInitVector = $crypt->getInitVector();
+
+ $expectedInitVector = str_repeat("\0", $this->getInitVectorSize($cipher, $mode));
+ $this->assertEquals($expectedInitVector, $actualInitVector);
+ }
+}
diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/SodiumChachaIetfTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/SodiumChachaIetfTest.php
new file mode 100644
index 000000000000..8092ce55b42c
--- /dev/null
+++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/SodiumChachaIetfTest.php
@@ -0,0 +1,51 @@
+encrypt($decrypted);
+
+ $this->assertNotEquals($encrypted, $result);
+ }
+
+ /**
+ * @dataProvider getCryptData
+ */
+ public function testDecrypt(string $key, string $encrypted, string $decrypted)
+ {
+ $crypt = new \Magento\Framework\Encryption\Adapter\SodiumChachaIetf($key);
+ $result = $crypt->decrypt($encrypted);
+
+ $this->assertEquals($decrypted, $result);
+ }
+}
diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/Crypt/_files/_sodium_chachaieft_fixtures.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/Crypt/_files/_sodium_chachaieft_fixtures.php
new file mode 100644
index 000000000000..7917bd9ba83e
--- /dev/null
+++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/Crypt/_files/_sodium_chachaieft_fixtures.php
@@ -0,0 +1,35 @@
+ [
+ 'key' => '6wRADHwwCBGgdxbcHhovGB0upmg0mbsN',
+ 'encrypted' => '146BhsQ3grT0VgkYuY3ii3gpClXHkFqlIcNpAD4+bAMBP+ToCHZHiJID',
+ 'decrypted' => 'Hello World!!!',
+ ],
+ 1 => [
+ 'key' => 'uPuzBU067DXTM4PqEi14Sv5tbWjVcRZI',
+ 'encrypted' => '6SQaVrCnY10n8tOxYyvWuVGKddjR12ZbGylM9K+bRHqsqltRwuLs15vV',
+ 'decrypted' => 'Hello World!!!',
+ ],
+ 2 => [
+ 'key' => 'zsmVdKkwVgylxMM8ZzQ3GTv7SxvusKnJ',
+ 'encrypted' => 'eQcREUJDV8EEB9WA1pBd5LbVQrs4Kyv6iWnkhOnjeitySuPQAcpIVoCM',
+ 'decrypted' => 'Hello World!!!',
+ ],
+ 3 => [
+ 'key' => 'aggaHLvRCxRRyebpsrGAdLAIfSrufYrN',
+ 'encrypted' => 'PSOa8KCpTsxnTgq4IKbpneF38FIp0JeAeiXQIf30vS5X+riylx05pz9b',
+ 'decrypted' => 'Hello World!!!',
+ ],
+ 4 => [
+ 'key' => '6tEWnKY6AcdjS2XfPe1DjTbkvu2cFFZo',
+ 'encrypted' => 'UglO9dEgslFpwPwejJmrK89PmBicv+I1pfdaXaEI69IrETD8LpdzOLF7',
+ 'decrypted' => 'Hello World!!!',
+ ],
+];
diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php
index a37dc981518e..c286cb0a1d80 100644
--- a/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php
+++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php
@@ -105,6 +105,7 @@ public function testConstructor($cipher, $mode)
*/
public function getConstructorExceptionData()
{
+ $key = substr(__CLASS__, -32, 32);
$result = [];
foreach (self::SUPPORTED_CIPHER_MODE_COMBINATIONS as $cipher => $modes) {
/** @var array $modes */
@@ -114,8 +115,8 @@ public function getConstructorExceptionData()
$tooLongInitVector = str_repeat('-', $this->_getInitVectorSize($cipher, $mode) + 1);
$result['tooLongKey-' . $cipher . '-' . $mode . '-false'] = [$tooLongKey, $cipher, $mode, false];
$keyPrefix = 'key-' . $cipher . '-' . $mode;
- $result[$keyPrefix . '-tooShortInitVector'] = [$this->_key, $cipher, $mode, $tooShortInitVector];
- $result[$keyPrefix . '-tooLongInitVector'] = [$this->_key, $cipher, $mode, $tooLongInitVector];
+ $result[$keyPrefix . '-tooShortInitVector'] = [$key, $cipher, $mode, $tooShortInitVector];
+ $result[$keyPrefix . '-tooLongInitVector'] = [$key, $cipher, $mode, $tooLongInitVector];
}
}
return $result;
diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php
index 74f2db9b05f2..f3853a123c15 100644
--- a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php
+++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php
@@ -3,13 +3,21 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Framework\Encryption\Test\Unit;
+use Magento\Framework\Encryption\Adapter\Mcrypt;
+use Magento\Framework\Encryption\Adapter\SodiumChachaIetf;
use Magento\Framework\Encryption\Encryptor;
use Magento\Framework\Encryption\Crypt;
class EncryptorTest extends \PHPUnit\Framework\TestCase
{
+ const CRYPT_KEY_1 = 'g9mY9KLrcuAVJfsmVUSRkKFLDdUPVkaZ';
+ const CRYPT_KEY_2 = '7wEjmrliuqZQ1NQsndSa8C8WHvddeEbN';
+
/**
* @var \Magento\Framework\Encryption\Encryptor
*/
@@ -27,7 +35,7 @@ protected function setUp()
$deploymentConfigMock->expects($this->any())
->method('get')
->with(Encryptor::PARAM_CRYPT_KEY)
- ->will($this->returnValue('cryptKey'));
+ ->will($this->returnValue(self::CRYPT_KEY_1));
$this->_model = new \Magento\Framework\Encryption\Encryptor($this->_randomGenerator, $deploymentConfigMock);
}
@@ -101,6 +109,7 @@ public function validateHashDataProvider()
* @param mixed $key
*
* @dataProvider encryptWithEmptyKeyDataProvider
+ * @expectedException \SodiumException
*/
public function testEncryptWithEmptyKey($key)
{
@@ -155,20 +164,27 @@ public function testEncrypt()
$actual = $this->_model->encrypt($data);
// Extract the initialization vector and encrypted data
- $parts = explode(':', $actual, 4);
- list(, , $iv, $encryptedData) = $parts;
+ $parts = explode(':', $actual, 3);
+ list(, , $encryptedData) = $parts;
- // Decrypt returned data with RIJNDAEL_256 cipher, cbc mode
- $crypt = new Crypt('cryptKey', MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $iv);
+ $crypt = new SodiumChachaIetf(self::CRYPT_KEY_1);
// Verify decrypted matches original data
$this->assertEquals($data, $crypt->decrypt(base64_decode((string)$encryptedData)));
}
public function testDecrypt()
+ {
+ $message = 'Mares eat oats and does eat oats, but little lambs eat ivy.';
+ $encrypted = $this->_model->encrypt($message);
+
+ $this->assertEquals($message, $this->_model->decrypt($encrypted));
+ }
+
+ public function testLegacyDecrypt()
{
// sample data to encrypt
$data = '0:2:z3a4ACpkU35W6pV692U4ueCVQP0m0v0p:' .
- '7ZPIIRZzQrgQH+csfF3fyxYNwbzPTwegncnoTxvI3OZyqKGYlOCTSx5i1KRqNemCC8kuCiOAttLpAymXhzjhNQ==';
+ 'DhEG8/uKGGq92ZusqrGb6X/9+2Ng0QZ9z2UZwljgJbs5/A3LaSnqcK0oI32yjHY49QJi+Z7q1EKu2yVqB8EMpA==';
$actual = $this->_model->decrypt($data);
@@ -177,7 +193,7 @@ public function testDecrypt()
list(, , $iv, $encrypted) = $parts;
// Decrypt returned data with RIJNDAEL_256 cipher, cbc mode
- $crypt = new Crypt('cryptKey', MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $iv);
+ $crypt = new Crypt(self::CRYPT_KEY_1, MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $iv);
// Verify decrypted matches original data
$this->assertEquals($encrypted, base64_encode($crypt->encrypt($actual)));
}
@@ -188,11 +204,11 @@ public function testEncryptDecryptNewKeyAdded()
$deploymentConfigMock->expects($this->at(0))
->method('get')
->with(Encryptor::PARAM_CRYPT_KEY)
- ->will($this->returnValue("cryptKey1"));
+ ->will($this->returnValue(self::CRYPT_KEY_1));
$deploymentConfigMock->expects($this->at(1))
->method('get')
->with(Encryptor::PARAM_CRYPT_KEY)
- ->will($this->returnValue("cryptKey1\ncryptKey2"));
+ ->will($this->returnValue(self::CRYPT_KEY_1 . "\n" . self::CRYPT_KEY_2));
$model1 = new Encryptor($this->_randomGenerator, $deploymentConfigMock);
// simulate an encryption key is being added
$model2 = new Encryptor($this->_randomGenerator, $deploymentConfigMock);
@@ -208,12 +224,15 @@ public function testEncryptDecryptNewKeyAdded()
public function testValidateKey()
{
- $actual = $this->_model->validateKey('some_key');
- $crypt = new Crypt('some_key', MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $actual->getInitVector());
- $expectedEncryptedData = base64_encode($crypt->encrypt('data'));
- $actualEncryptedData = base64_encode($actual->encrypt('data'));
- $this->assertEquals($expectedEncryptedData, $actualEncryptedData);
- $this->assertEquals($crypt->decrypt($expectedEncryptedData), $actual->decrypt($actualEncryptedData));
+ $this->_model->validateKey(self::CRYPT_KEY_1);
+ }
+
+ /**
+ * @expectedException \Exception
+ */
+ public function testValidateKeyInvalid()
+ {
+ $this->_model->validateKey('----- ');
}
/**
diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js
index 9528e023d3ee..09ceafc011d6 100644
--- a/lib/web/mage/adminhtml/browser.js
+++ b/lib/web/mage/adminhtml/browser.js
@@ -434,7 +434,7 @@ define([
showLoader: true
}).done($.proxy(function () {
self.reload();
- self.element.find('#delete_files').toggleClass(self.options.hidden, true);
+ self.element.find('#delete_files, #insert_files').toggleClass(self.options.hidden, true);
$(window).trigger('fileDeleted.mediabrowser', {
ids: ids