Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add SecretsRevealCommand #53466

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

Merged
merged 1 commit into from
Feb 3, 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
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed when the class is final?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea. I just copied it from SecretsListCommand (as this command is based on it).

I don't think it makes much sense on either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OskarStark That gives us more flexibility 👍 as there is no need to use that class from userland, only the command itself (via CLI) matters

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't get what you mean.

I can do absolutely the same without the internal tag, the class is final and I can only execute the command in userland

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess what @chalasr means is that we could also rename this class or move it to another namespace without having to worry about consumers of it. Not sure if we really need that flexibility though tbh.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok got it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we really need that flexibility though tbh.

👍 Thinking about it, I agree we should not fall in being too much defensive in general.

*/
#[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 <info>%command.name%</info> command reveals a stored secret.

<info>%command.full_name%</info>
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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)));
}
}