Skip to content

Commit

Permalink
Merge branch 'Maksold-feature/issue-73-encrypt-resolver'
Browse files Browse the repository at this point in the history
  • Loading branch information
therouv committed Apr 18, 2024
2 parents f7fd4b3 + aa2fbe0 commit 96c7751
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
64 changes: 64 additions & 0 deletions Model/Resolver/EncryptResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php
/**
* Copyright © semaio GmbH. All rights reserved.
* See LICENSE.md bundled with this module for license details.
*/

namespace Semaio\ConfigImportExport\Model\Resolver;

use Magento\Framework\Encryption\EncryptorInterface;
use Semaio\ConfigImportExport\Exception\UnresolveableValueException;
use function strlen;

class EncryptResolver extends AbstractResolver
{
/**
* @var EncryptorInterface
*/
private $encryptor;

public function __construct(EncryptorInterface $encryptor)
{
$this->encryptor = $encryptor;
}

/**
* Resolve the config value if wrapped with '%encrypt(value)%', this method encrypts the value.
*
* @param string|null $value
* @param string|null $configPath
*
* @return string|null
*
* @throws UnresolveableValueException
*/
public function resolve($value, $configPath = null)
{
if ($value === null) {
return null;
}

$value = (string)$value;
if ($value === '%encrypt()%') {
throw new UnresolveableValueException('Please specify a valid value to encrypt.');
}

$valueToEncrypt = preg_replace_callback(
'/\%encrypt\(([^)]+)\)\%/',
function ($matches) {
return $matches[1];
},
$value
);

return $this->encryptor->encrypt($valueToEncrypt);
}

/**
* @inheritDoc
*/
public function supports($value, $configPath = null): bool
{
return 0 === strncmp((string)$value, '%encrypt', strlen('%encrypt'));
}
}
112 changes: 112 additions & 0 deletions Test/Unit/Model/Resolver/EncryptResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php
/**
* Copyright © semaio GmbH. All rights reserved.
* See LICENSE.md bundled with this module for license details.
*/

namespace Semaio\ConfigImportExport\Test\Unit\Model\Validator;

use Generator;
use Magento\Framework\Encryption\EncryptorInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Semaio\ConfigImportExport\Exception\UnresolveableValueException;
use Semaio\ConfigImportExport\Model\Resolver\EncryptResolver;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class EncryptResolverTest extends TestCase
{
/**
* @var InputInterface
*/
private $input;

/**
* @var OutputInterface
*/
private $output;

/**
* @var QuestionHelper
*/
private $questionHelper;

/**
* @var MockObject|EncryptorInterface
*/
private $encryptor;

/**
* Set up test class
*/
protected function setUp(): void
{
parent::setUp();

$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
$this->questionHelper = $this->createMock(QuestionHelper::class);
$this->encryptor = $this->createMock(EncryptorInterface::class);
}

/**
* @test
*
* @dataProvider resolveDataProvider
*/
public function validate($value, $expectedResult): void
{
$this->encryptor->expects($this->any())
->method('encrypt')
->with($expectedResult)
->willReturn($expectedResult);

$this->assertEquals($this->getEncryptResolver()->resolve($value), $expectedResult);
}

public function resolveDataProvider(): Generator
{
yield [
'test_without_data_to_encrypt',
'test_without_data_to_encrypt',
];
yield [
'%encrypt(data_to_encrypt)%',
'data_to_encrypt',
];
yield [
null,
'',
];
yield [
false,
'',
];
yield [
true,
'1',
];
}

public function testItWillRaiseErrorIfEncryptValueIsEmpty(): void
{
$this->expectException(UnresolveableValueException::class);

$this->getEncryptResolver()->resolve('%encrypt()%');
}

/**
* @return EncryptResolver
*/
private function getEncryptResolver()
{
$resolver = new EncryptResolver($this->encryptor);
$resolver->setInput($this->input);
$resolver->setOutput($this->output);
$resolver->setQuestionHelper($this->questionHelper);

return $resolver;
}
}
14 changes: 14 additions & 0 deletions docs/config-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ vendorx/general/api_key:
You can then set the environment variable `VENDORX_API_KEY` in your CI/CD configuration to the secret API key.
### Encryption Value Substitution
For importing encrypted configuration data, such as passwords and API keys, into fields utilizing Magento's `\Magento\Config\Model\Config\Backend\Encrypted` backend model, use `%encrypt(value)%` (make sure to put quotes around it) placeholder within your configuration files.
For example, this could be the content of your configuration file:
```
payment/provider/secret_key:
default:
0: '%encrypt(mySecretKey)%'
```
:exclamation: It is generally not recommended to store sensitive data in your GIT repository but instead keep it securely in the environment's database. Please use this option with caution and at your own risk.
### Delete Config
Sometimes, it might be helpful to be able to delete certain config values and get back to the default behavior. To do so, your config value has to be a magic-ish string.
Expand Down
1 change: 1 addition & 0 deletions etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<argument name="resolvers" xsi:type="array">
<item name="environmentVariableResolver" xsi:type="object">Semaio\ConfigImportExport\Model\Resolver\EnvironmentVariableResolver</item>
<item name="themePathResolver" xsi:type="object">Semaio\ConfigImportExport\Model\Resolver\ThemePathResolver</item>
<item name="encryptResolver" xsi:type="object">Semaio\ConfigImportExport\Model\Resolver\EncryptResolver</item>
</argument>
</arguments>
</type>
Expand Down

0 comments on commit 96c7751

Please sign in to comment.