Skip to content

Commit a612a4b

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 e9381cc + 101086e commit a612a4b

File tree

14 files changed

+300
-19
lines changed

14 files changed

+300
-19
lines changed

src/Symfony/Bundle/FrameworkBundle/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+
}

src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ public function downloadPackages(array $importMapEntries, ?callable $progressCal
184184
$errors = [];
185185
$contents = [];
186186
$extraFileResponses = [];
187+
/** @var ImportMapEntry $entry */
187188
foreach ($responses as $package => [$response, $entry]) {
188189
if (200 !== $response->getStatusCode()) {
189190
$errors[] = [$package, $response];
@@ -196,7 +197,6 @@ public function downloadPackages(array $importMapEntries, ?callable $progressCal
196197

197198
$dependencies = [];
198199
$extraFiles = [];
199-
/** @var ImportMapEntry $entry */
200200
$contents[$package] = [
201201
'content' => $this->makeImportsBare($response->getContent(), $dependencies, $extraFiles, $entry->type, $entry->getPackagePathString()),
202202
'dependencies' => $dependencies,

src/Symfony/Component/Console/Formatter/OutputFormatter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,6 @@ private function addLineBreaks(string $text, int $width): string
275275
{
276276
$encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8';
277277

278-
return b($text)->toCodePointString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding);
278+
return b($text)->toUnicodeString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding);
279279
}
280280
}

src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ public function testFormatAndWrap()
368368
$this->assertSame("foobar\e[37;41mbaz\e[39;49m\n\e[37;41mnewline\e[39;49m", $formatter->formatAndWrap("foobar<error>baz\nnewline</error>", 11));
369369
$this->assertSame("foobar\e[37;41mbazne\e[39;49m\n\e[37;41mwline\e[39;49m", $formatter->formatAndWrap("foobar<error>bazne\nwline</error>", 11));
370370
$this->assertSame("foobar\e[37;41mbazne\e[39;49m\n\e[37;41mw\e[39;49m\n\e[37;41mline\e[39;49m", $formatter->formatAndWrap("foobar<error>baznew\nline</error>", 11));
371+
$this->assertSame("\e[37;41m👩‍🌾\e[39;49m", $formatter->formatAndWrap('<error>👩‍🌾</error>', 1));
371372

372373
$formatter = new OutputFormatter();
373374

@@ -387,6 +388,7 @@ public function testFormatAndWrap()
387388
$this->assertSame("foobarbaz\nnewline", $formatter->formatAndWrap("foobar<error>baz\nnewline</error>", 11));
388389
$this->assertSame("foobarbazne\nwline", $formatter->formatAndWrap("foobar<error>bazne\nwline</error>", 11));
389390
$this->assertSame("foobarbazne\nw\nline", $formatter->formatAndWrap("foobar<error>baznew\nline</error>", 11));
391+
$this->assertSame('👩‍🌾', $formatter->formatAndWrap('👩‍🌾', 1));
390392
}
391393
}
392394

src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ public function __construct(
3333
private ?FormRendererInterface $formRenderer = null,
3434
private ?TranslatorInterface $translator = null,
3535
) {
36+
/** @var ClassMetadata $metadata */
3637
$metadata = $validator->getMetadataFor(\Symfony\Component\Form\Form::class);
3738

3839
// Register the form constraints in the validator programmatically.
3940
// This functionality is required when using the Form component without
4041
// the DIC, where the XML file is loaded automatically. Thus the following
4142
// code must be kept synchronized with validation.xml
4243

43-
/** @var ClassMetadata $metadata */
4444
$metadata->addConstraint(new Form());
4545
$metadata->addConstraint(new Traverse(false));
4646
}

src/Symfony/Component/Form/ResolvedFormType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ public function finishView(FormView $view, FormInterface $form, array $options):
117117

118118
$this->innerType->finishView($view, $form, $options);
119119

120+
/** @var FormTypeExtensionInterface $extension */
120121
foreach ($this->typeExtensions as $extension) {
121-
/** @var FormTypeExtensionInterface $extension */
122122
$extension->finishView($view, $form, $options);
123123
}
124124
}

src/Symfony/Component/Mailer/Tests/EventListener/MessengerTransportListenerTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ public function testMessengerTransportStampViaHeader()
4141
$event = new MessageEvent($message, $envelope, 'smtp', true);
4242
$l->onMessage($event);
4343
$this->assertCount(1, $event->getStamps());
44-
/** @var TransportNamesStamp $stamp */
4544
$this->assertInstanceOf(TransportNamesStamp::class, $stamp = $event->getStamps()[0]);
4645
$this->assertSame(['async'], $stamp->getTransportNames());
4746
$this->assertFalse($message->getHeaders()->has('X-Bus-Transport'));
@@ -57,7 +56,6 @@ public function testMessengerTransportStampsViaHeader()
5756
$event = new MessageEvent($message, $envelope, 'smtp', true);
5857
$l->onMessage($event);
5958
$this->assertCount(1, $event->getStamps());
60-
/** @var TransportNamesStamp $stamp */
6159
$this->assertInstanceOf(TransportNamesStamp::class, $stamp = $event->getStamps()[0]);
6260
$this->assertSame(['async', 'async1', $name], $stamp->getTransportNames());
6361
$this->assertFalse($message->getHeaders()->has('X-Bus-Transport'));

0 commit comments

Comments
 (0)