From 776875d4521c7cbc7a005e7eae3a8919a8bf0c9f Mon Sep 17 00:00:00 2001
From: Daniel Burger <48986191+danielburger1337@users.noreply.github.com>
Date: Tue, 9 Jan 2024 08:02:56 +0100
Subject: [PATCH] Add `SecretsRevealCommand`
---
.../Bundle/FrameworkBundle/CHANGELOG.md | 1 +
.../Command/SecretsRevealCommand.php | 72 ++++++++++++++++
.../FrameworkExtension.php | 1 +
.../Resources/config/console.php | 8 ++
.../Command/SecretsRevealCommandTest.php | 86 +++++++++++++++++++
5 files changed, 168 insertions(+)
create mode 100644 src/Symfony/Bundle/FrameworkBundle/Command/SecretsRevealCommand.php
create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRevealCommandTest.php
diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index 43d7f62950e83..cc2151d6addbf 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -9,6 +9,7 @@ CHANGELOG
* Move the Router `cache_dir` to `kernel.build_dir`
* Deprecate the `router.cache_dir` config option
* Add `rate_limiter` tags to rate limiter services
+ * Add `secrets:reveal` command
7.0
---
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRevealCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRevealCommand.php
new file mode 100644
index 0000000000000..bcbdea11f079c
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRevealCommand.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\Command;
+
+use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * @internal
+ */
+#[AsCommand(name: 'secrets:reveal', description: 'Reveal the value of a secret')]
+final class SecretsRevealCommand extends Command
+{
+ public function __construct(
+ private readonly AbstractVault $vault,
+ private readonly ?AbstractVault $localVault = null,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void
+ {
+ $this
+ ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret to reveal', null, fn () => array_keys($this->vault->list()))
+ ->setHelp(<<<'EOF'
+The %command.name% command reveals a stored secret.
+
+ %command.full_name%
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
+
+ $secrets = $this->vault->list(true);
+ $localSecrets = $this->localVault?->list(true);
+
+ $name = (string) $input->getArgument('name');
+
+ if (null !== $localSecrets && \array_key_exists($name, $localSecrets)) {
+ $io->writeln($localSecrets[$name]);
+ } else {
+ if (!\array_key_exists($name, $secrets)) {
+ $io->error(sprintf('The secret "%s" does not exist.', $name));
+
+ return self::INVALID;
+ }
+
+ $io->writeln($secrets[$name]);
+ }
+
+ return self::SUCCESS;
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 095d782aa12a3..38de7421dacc2 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -1773,6 +1773,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c
if (!$this->readConfigEnabled('secrets', $container, $config)) {
$container->removeDefinition('console.command.secrets_set');
$container->removeDefinition('console.command.secrets_list');
+ $container->removeDefinition('console.command.secrets_reveal');
$container->removeDefinition('console.command.secrets_remove');
$container->removeDefinition('console.command.secrets_generate_key');
$container->removeDefinition('console.command.secrets_decrypt_to_local');
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php
index 334d20426c68c..b4f7dfcf3ea5e 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php
@@ -33,6 +33,7 @@
use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand;
+use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand;
use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand;
use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand;
@@ -355,6 +356,13 @@
])
->tag('console.command')
+ ->set('console.command.secrets_reveal', SecretsRevealCommand::class)
+ ->args([
+ service('secrets.vault'),
+ service('secrets.local_vault')->ignoreOnInvalid(),
+ ])
+ ->tag('console.command')
+
->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class)
->args([
service('secrets.vault'),
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRevealCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRevealCommandTest.php
new file mode 100644
index 0000000000000..94643db2c92c5
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRevealCommandTest.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\Tests\Command;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand;
+use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
+use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Tester\CommandTester;
+
+class SecretsRevealCommandTest extends TestCase
+{
+ public function testExecute()
+ {
+ $vault = $this->createMock(AbstractVault::class);
+ $vault->method('list')->willReturn(['secretKey' => 'secretValue']);
+
+ $command = new SecretsRevealCommand($vault);
+
+ $tester = new CommandTester($command);
+ $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey']));
+
+ $this->assertEquals('secretValue', trim($tester->getDisplay(true)));
+ }
+
+ public function testInvalidName()
+ {
+ $vault = $this->createMock(AbstractVault::class);
+ $vault->method('list')->willReturn(['secretKey' => 'secretValue']);
+
+ $command = new SecretsRevealCommand($vault);
+
+ $tester = new CommandTester($command);
+ $this->assertSame(Command::INVALID, $tester->execute(['name' => 'undefinedKey']));
+
+ $this->assertStringContainsString('The secret "undefinedKey" does not exist.', trim($tester->getDisplay(true)));
+ }
+
+ /**
+ * @backupGlobals enabled
+ */
+ public function testLocalVaultOverride()
+ {
+ $vault = $this->createMock(AbstractVault::class);
+ $vault->method('list')->willReturn(['secretKey' => 'secretValue']);
+
+ $_ENV = ['secretKey' => 'newSecretValue'];
+ $localVault = new DotenvVault('/not/a/path');
+
+ $command = new SecretsRevealCommand($vault, $localVault);
+
+ $tester = new CommandTester($command);
+ $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey']));
+
+ $this->assertEquals('newSecretValue', trim($tester->getDisplay(true)));
+ }
+
+ /**
+ * @backupGlobals enabled
+ */
+ public function testOnlyLocalVaultContainsName()
+ {
+ $vault = $this->createMock(AbstractVault::class);
+ $vault->method('list')->willReturn(['otherKey' => 'secretValue']);
+
+ $_ENV = ['secretKey' => 'secretValue'];
+ $localVault = new DotenvVault('/not/a/path');
+
+ $command = new SecretsRevealCommand($vault, $localVault);
+
+ $tester = new CommandTester($command);
+ $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey']));
+
+ $this->assertEquals('secretValue', trim($tester->getDisplay(true)));
+ }
+}