Skip to content

Commit f515f3d

Browse files
Merge branch '7.3' into 7.4
* 7.3: Fix inline var annotations fix expected stream to native value transformers [Console][Table] Don't split grapheme clusters [FrameworkBundle] Fix block type from `OK` to `ERROR` when local vault is disabled in `SecretsGenerateKeysCommand` [FrameworkBundle] Add tests for `secrets:decrypt-to-local`, `encrypt-from-local`, and `generate-keys` commands Reflection*::setAccessible() has no effect as of PHP 8.1
2 parents 6f5f9d2 + d19b87b commit f515f3d

File tree

4 files changed

+294
-1
lines changed

4 files changed

+294
-1
lines changed

Command/SecretsGenerateKeysCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6363
$vault = $input->getOption('local') ? $this->localVault : $this->vault;
6464

6565
if (null === $vault) {
66-
$io->success('The local vault is disabled.');
66+
$io->error('The local vault is disabled.');
6767

6868
return 1;
6969
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Command;
13+
14+
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand;
17+
use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault;
18+
use Symfony\Component\Console\Tester\CommandTester;
19+
use Symfony\Component\Filesystem\Filesystem;
20+
21+
#[RequiresPhpExtension('sodium')]
22+
class SecretsDecryptToLocalCommandTest extends TestCase
23+
{
24+
private string $mainDir;
25+
private string $localDir;
26+
27+
protected function setUp(): void
28+
{
29+
$this->mainDir = sys_get_temp_dir().'/sf_secrets/main/';
30+
$this->localDir = sys_get_temp_dir().'/sf_secrets/local/';
31+
32+
$fs = new Filesystem();
33+
$fs->remove([$this->mainDir, $this->localDir]);
34+
35+
$mainVault = new SodiumVault($this->mainDir);
36+
$mainVault->generateKeys();
37+
$mainVault->seal('FOO_SECRET', 'super_secret_value');
38+
39+
$localVault = new SodiumVault($this->localDir);
40+
$localVault->generateKeys();
41+
}
42+
43+
protected function tearDown(): void
44+
{
45+
(new Filesystem())->remove([$this->mainDir, $this->localDir]);
46+
}
47+
48+
public function testSecretsAreDecryptedAndStoredInLocalVault()
49+
{
50+
$mainVault = new SodiumVault($this->mainDir);
51+
$localVault = new SodiumVault($this->localDir);
52+
$tester = new CommandTester(new SecretsDecryptToLocalCommand($mainVault, $localVault));
53+
54+
$this->assertSame(0, $tester->execute([]));
55+
$this->assertStringContainsString('1 secret found in the vault.', $tester->getDisplay());
56+
$this->assertStringContainsString('Secret "FOO_SECRET" encrypted', $tester->getDisplay());
57+
58+
$this->assertArrayHasKey('FOO_SECRET', $localVault->list(true));
59+
$this->assertSame('super_secret_value', $localVault->reveal('FOO_SECRET'));
60+
}
61+
62+
public function testExistingLocalSecretsAreSkippedWithoutForce()
63+
{
64+
$mainVault = new SodiumVault($this->mainDir);
65+
$localVault = new SodiumVault($this->localDir);
66+
$localVault->seal('FOO_SECRET', 'old_value');
67+
$tester = new CommandTester(new SecretsDecryptToLocalCommand($mainVault, $localVault));
68+
69+
$this->assertSame(0, $tester->execute([]));
70+
$this->assertStringContainsString('1 secret is already overridden in the local vault and will be skipped.', $tester->getDisplay());
71+
$this->assertSame('old_value', $localVault->reveal('FOO_SECRET'));
72+
}
73+
74+
public function testForceOptionOverridesLocalSecrets()
75+
{
76+
$mainVault = new SodiumVault($this->mainDir);
77+
$localVault = new SodiumVault($this->localDir);
78+
$localVault->seal('FOO_SECRET', 'old_value');
79+
$tester = new CommandTester(new SecretsDecryptToLocalCommand($mainVault, $localVault));
80+
81+
$this->assertSame(0, $tester->execute(['--force' => true]));
82+
$this->assertStringContainsString('Secret "FOO_SECRET" encrypted', $tester->getDisplay());
83+
$this->assertSame('super_secret_value', $localVault->reveal('FOO_SECRET'));
84+
}
85+
86+
public function testFailsGracefullyWhenLocalVaultIsDisabled()
87+
{
88+
$mainVault = new SodiumVault($this->mainDir);
89+
$tester = new CommandTester(new SecretsDecryptToLocalCommand($mainVault, null));
90+
91+
$this->assertSame(1, $tester->execute([]));
92+
$this->assertStringContainsString('The local vault is disabled.', $tester->getDisplay());
93+
}
94+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Command;
13+
14+
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand;
17+
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
18+
use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault;
19+
use Symfony\Component\Console\Tester\CommandTester;
20+
use Symfony\Component\Filesystem\Filesystem;
21+
22+
#[RequiresPhpExtension('sodium')]
23+
class SecretsEncryptFromLocalCommandTest extends TestCase
24+
{
25+
private string $vaultDir;
26+
private string $localVaultDir;
27+
private Filesystem $fs;
28+
29+
protected function setUp(): void
30+
{
31+
$this->vaultDir = sys_get_temp_dir().'/sf_secrets/vault_'.uniqid();
32+
$this->localVaultDir = sys_get_temp_dir().'/sf_secrets/local_'.uniqid();
33+
$this->fs = new Filesystem();
34+
$this->fs->remove([$this->vaultDir, $this->localVaultDir]);
35+
}
36+
37+
protected function tearDown(): void
38+
{
39+
$this->fs->remove([$this->vaultDir, $this->localVaultDir]);
40+
}
41+
42+
public function testFailsWhenLocalVaultIsDisabled()
43+
{
44+
$vault = $this->createMock(AbstractVault::class);
45+
$command = new SecretsEncryptFromLocalCommand($vault, null);
46+
$tester = new CommandTester($command);
47+
48+
$this->assertSame(1, $tester->execute([]));
49+
$this->assertStringContainsString('The local vault is disabled.', $tester->getDisplay());
50+
}
51+
52+
public function testEncryptsLocalOverrides()
53+
{
54+
$vault = new SodiumVault($this->vaultDir);
55+
$vault->generateKeys();
56+
57+
$localVault = new SodiumVault($this->localVaultDir);
58+
$localVault->generateKeys();
59+
60+
$vault->seal('MY_SECRET', 'prod-value');
61+
$localVault->seal('MY_SECRET', 'local-value');
62+
63+
$command = new SecretsEncryptFromLocalCommand($vault, $localVault);
64+
$tester = new CommandTester($command);
65+
66+
$exitCode = $tester->execute([]);
67+
$this->assertSame(0, $exitCode);
68+
69+
$revealed = $vault->reveal('MY_SECRET');
70+
$this->assertSame('local-value', $revealed);
71+
}
72+
73+
public function testDoesNotSealIfSameValue()
74+
{
75+
$vault = new SodiumVault($this->vaultDir);
76+
$vault->generateKeys();
77+
78+
$localVault = new SodiumVault($this->localVaultDir);
79+
$localVault->generateKeys();
80+
81+
$vault->seal('SHARED_SECRET', 'same-value');
82+
$localVault->seal('SHARED_SECRET', 'same-value');
83+
84+
$command = new SecretsEncryptFromLocalCommand($vault, $localVault);
85+
$tester = new CommandTester($command);
86+
87+
$exitCode = $tester->execute([]);
88+
$this->assertSame(0, $exitCode);
89+
90+
$revealed = $vault->reveal('SHARED_SECRET');
91+
$this->assertSame('same-value', $revealed);
92+
}
93+
94+
public function testFailsIfLocalSecretIsMissing()
95+
{
96+
$vault = new SodiumVault($this->vaultDir);
97+
$vault->generateKeys();
98+
99+
$localVault = new SodiumVault($this->localVaultDir);
100+
$localVault->generateKeys();
101+
102+
$vault->seal('MISSING_IN_LOCAL', 'prod-only');
103+
104+
$command = new SecretsEncryptFromLocalCommand($vault, $localVault);
105+
$tester = new CommandTester($command);
106+
107+
$this->assertSame(1, $tester->execute([]));
108+
$this->assertStringContainsString('Secret "MISSING_IN_LOCAL" not found', $tester->getDisplay());
109+
}
110+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Command;
13+
14+
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand;
17+
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
18+
use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault;
19+
use Symfony\Component\Console\Tester\CommandTester;
20+
use Symfony\Component\Filesystem\Filesystem;
21+
22+
#[RequiresPhpExtension('sodium')]
23+
class SecretsGenerateKeysCommandTest extends TestCase
24+
{
25+
private string $secretsDir;
26+
private const ENC_KEY_FILE = 'test.encrypt.public.php';
27+
private const DEC_KEY_FILE = 'test.decrypt.private.php';
28+
29+
protected function setUp(): void
30+
{
31+
$this->secretsDir = sys_get_temp_dir().'/sf_secrets/test/';
32+
(new Filesystem())->remove($this->secretsDir);
33+
}
34+
35+
protected function tearDown(): void
36+
{
37+
(new Filesystem())->remove($this->secretsDir);
38+
}
39+
40+
public function testItGeneratesSodiumKeys()
41+
{
42+
$vault = new SodiumVault($this->secretsDir);
43+
$tester = new CommandTester(new SecretsGenerateKeysCommand($vault));
44+
45+
$this->assertSame(0, $tester->execute([]));
46+
$this->assertKeysExistAndReadable();
47+
}
48+
49+
public function testItRotatesSodiumKeysWhenRequested()
50+
{
51+
$vault = new SodiumVault($this->secretsDir);
52+
$tester = new CommandTester(new SecretsGenerateKeysCommand($vault));
53+
54+
$this->assertSame(0, $tester->execute(['--rotate' => true]));
55+
$this->assertKeysExistAndReadable();
56+
}
57+
58+
public function testItFailsGracefullyWhenLocalVaultIsDisabled()
59+
{
60+
$vault = $this->createMock(AbstractVault::class);
61+
$tester = new CommandTester(new SecretsGenerateKeysCommand($vault));
62+
63+
$this->assertSame(1, $tester->execute(['--local' => true]));
64+
$this->assertStringContainsString('The local vault is disabled.', $tester->getDisplay());
65+
}
66+
67+
public function testFailsWhenKeysAlreadyExistAndRotateNotPassed()
68+
{
69+
$vault = new SodiumVault($this->secretsDir);
70+
$vault->generateKeys();
71+
72+
$command = new SecretsGenerateKeysCommand($vault);
73+
$tester = new CommandTester($command);
74+
75+
$this->assertSame(1, $tester->execute([]));
76+
$this->assertStringContainsString('Sodium keys already exist at', $tester->getDisplay());
77+
}
78+
79+
private function assertKeysExistAndReadable(): void
80+
{
81+
$encPath = $this->secretsDir.'/'.self::ENC_KEY_FILE;
82+
$decPath = $this->secretsDir.'/'.self::DEC_KEY_FILE;
83+
84+
$this->assertFileExists($encPath, 'Encryption key file does not exist.');
85+
$this->assertFileExists($decPath, 'Decryption key file does not exist.');
86+
$this->assertNotFalse(@file_get_contents($encPath), 'Encryption key file is not readable.');
87+
$this->assertNotFalse(@file_get_contents($decPath), 'Decryption key file is not readable.');
88+
}
89+
}

0 commit comments

Comments
 (0)