Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement EncryptResolver (#73) #74

Merged
merged 1 commit into from
Apr 18, 2024
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
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;
}
}
12 changes: 12 additions & 0 deletions docs/config-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ 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)%'
```

### 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
Loading