diff --git a/Model/Resolver/EncryptResolver.php b/Model/Resolver/EncryptResolver.php new file mode 100644 index 0000000..4477427 --- /dev/null +++ b/Model/Resolver/EncryptResolver.php @@ -0,0 +1,64 @@ +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')); + } +} diff --git a/Test/Unit/Model/Resolver/EncryptResolverTest.php b/Test/Unit/Model/Resolver/EncryptResolverTest.php new file mode 100644 index 0000000..2b75ed0 --- /dev/null +++ b/Test/Unit/Model/Resolver/EncryptResolverTest.php @@ -0,0 +1,112 @@ +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; + } +} diff --git a/docs/config-import.md b/docs/config-import.md index 05bfd53..bc65780 100644 --- a/docs/config-import.md +++ b/docs/config-import.md @@ -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. diff --git a/etc/di.xml b/etc/di.xml index 83d7251..ac6e2e9 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -17,6 +17,7 @@ Semaio\ConfigImportExport\Model\Resolver\EnvironmentVariableResolver Semaio\ConfigImportExport\Model\Resolver\ThemePathResolver + Semaio\ConfigImportExport\Model\Resolver\EncryptResolver