Skip to content

Commit 68821ae

Browse files
committed
minor #2462 [LiveComponent] Allow configuring secret for fingerprints and checksums (smnandre)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [LiveComponent] Allow configuring secret for fingerprints and checksums | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Issues | Fix #2453 | License | MIT Allow to configure a dedicated secret (used in FingerprintCalculator and LiveComonentHydrator) Suggested by `@dkarlovi` in #2453 Implementation inspired by [symfony #56840](symfony/symfony#56840) Should be merged _after_ #2461 Commits ------- a641a2e [LiveComponent] Allow configuring secret for fingerprints and checksums
2 parents a63464e + a641a2e commit 68821ae

File tree

4 files changed

+163
-6
lines changed

4 files changed

+163
-6
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 2.23.0
4+
5+
- Allow configuring the secret used to compute fingerprints and checksums.
6+
37
## 2.22.0
48

59
- Remove CSRF tokens - rely on same-origin/CORS instead

src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
namespace Symfony\UX\LiveComponent\DependencyInjection;
1313

1414
use Symfony\Component\AssetMapper\AssetMapperInterface;
15+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
16+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
17+
use Symfony\Component\Config\Definition\ConfigurationInterface;
18+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1519
use Symfony\Component\DependencyInjection\ChildDefinition;
1620
use Symfony\Component\DependencyInjection\ContainerBuilder;
1721
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -50,14 +54,12 @@
5054
use Symfony\UX\TwigComponent\ComponentFactory;
5155
use Symfony\UX\TwigComponent\ComponentRenderer;
5256

53-
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;
54-
5557
/**
5658
* @author Kevin Bond <kevinbond@gmail.com>
5759
*
5860
* @internal
5961
*/
60-
final class LiveComponentExtension extends Extension implements PrependExtensionInterface
62+
final class LiveComponentExtension extends Extension implements PrependExtensionInterface, ConfigurationInterface
6163
{
6264
public const TEMPLATES_MAP_FILENAME = 'live_components_twig_templates.map';
6365

@@ -93,16 +95,19 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
9395
}
9496
);
9597

98+
$configuration = $this->getConfiguration($configs, $container);
99+
$config = $this->processConfiguration($configuration, $configs);
100+
96101
$container->registerForAutoconfiguration(HydrationExtensionInterface::class)
97102
->addTag(LiveComponentBundle::HYDRATION_EXTENSION_TAG);
98103

99104
$container->register('ux.live_component.component_hydrator', LiveComponentHydrator::class)
100105
->setArguments([
101-
tagged_iterator(LiveComponentBundle::HYDRATION_EXTENSION_TAG),
106+
new TaggedIteratorArgument(LiveComponentBundle::HYDRATION_EXTENSION_TAG),
102107
new Reference('property_accessor'),
103108
new Reference('ux.live_component.metadata_factory'),
104109
new Reference('serializer', ContainerInterface::NULL_ON_INVALID_REFERENCE),
105-
'%kernel.secret%',
110+
$config['secret'], // defaults to '%kernel.secret%'
106111
])
107112
;
108113

@@ -236,7 +241,7 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
236241

237242
$container->register('ux.live_component.deterministic_id_calculator', DeterministicTwigIdCalculator::class);
238243
$container->register('ux.live_component.fingerprint_calculator', FingerprintCalculator::class)
239-
->setArguments(['%kernel.secret%']);
244+
->setArguments([$config['secret']]); // default to %kernel.secret%
240245

241246
$container->setAlias(ComponentValidatorInterface::class, ComponentValidator::class);
242247

@@ -258,6 +263,35 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
258263
->addTag('kernel.cache_warmer');
259264
}
260265

266+
public function getConfigTreeBuilder(): TreeBuilder
267+
{
268+
$treeBuilder = new TreeBuilder('live_component');
269+
$rootNode = $treeBuilder->getRootNode();
270+
\assert($rootNode instanceof ArrayNodeDefinition);
271+
272+
$rootNode
273+
->addDefaultsIfNotSet()
274+
->children()
275+
->scalarNode('secret')
276+
->info('The secret used to compute fingerprints and checksums')
277+
->beforeNormalization()
278+
->ifString()
279+
->then(trim(...))
280+
->end()
281+
->cannotBeEmpty()
282+
->defaultValue('%kernel.secret%')
283+
->end()
284+
->end()
285+
;
286+
287+
return $treeBuilder;
288+
}
289+
290+
public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface
291+
{
292+
return $this;
293+
}
294+
261295
private function isAssetMapperAvailable(ContainerBuilder $container): bool
262296
{
263297
if (!interface_exists(AssetMapperInterface::class)) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\UX\LiveComponent\Tests\Unit\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
16+
use Symfony\Component\Config\Definition\Processor;
17+
use Symfony\UX\LiveComponent\DependencyInjection\LiveComponentExtension;
18+
19+
class LiveComponentConfigurationTest extends TestCase
20+
{
21+
public function testDefaultSecret()
22+
{
23+
$processor = new Processor();
24+
$config = $processor->processConfiguration(new LiveComponentExtension(), []);
25+
26+
$this->assertEquals('%kernel.secret%', $config['secret']);
27+
}
28+
29+
public function testEmptySecretThrows()
30+
{
31+
$this->expectException(InvalidConfigurationException::class);
32+
$this->expectExceptionMessage('The path "live_component.secret" cannot contain an empty value, but got null.');
33+
34+
$processor = new Processor();
35+
$config = $processor->processConfiguration(new LiveComponentExtension(), [
36+
'live_component' => [
37+
'secret' => null,
38+
],
39+
]);
40+
}
41+
42+
public function testCustomSecret()
43+
{
44+
$processor = new Processor();
45+
$config = $processor->processConfiguration(new LiveComponentExtension(), [
46+
'live_component' => [
47+
'secret' => 'my_secret',
48+
],
49+
]);
50+
51+
$this->assertEquals('my_secret', $config['secret']);
52+
}
53+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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\UX\LiveComponent\Tests\Unit\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
17+
use Symfony\UX\LiveComponent\DependencyInjection\LiveComponentExtension;
18+
19+
class LiveComponentExtensionTest extends TestCase
20+
{
21+
public function testKernelSecretIsUsedByDefault(): void
22+
{
23+
$container = $this->createContainer();
24+
$container->registerExtension(new LiveComponentExtension());
25+
$container->loadFromExtension('live_component', []);
26+
$this->compileContainer($container);
27+
28+
$this->assertSame('%kernel.secret%', $container->getDefinition('ux.live_component.component_hydrator')->getArgument(4));
29+
$this->assertSame('%kernel.secret%', $container->getDefinition('ux.live_component.fingerprint_calculator')->getArgument(0));
30+
}
31+
32+
public function testCustomSecretIsUsedInDefinition(): void
33+
{
34+
$container = $this->createContainer();
35+
$container->registerExtension(new LiveComponentExtension());
36+
$container->loadFromExtension('live_component', [
37+
'secret' => 'custom_secret',
38+
]);
39+
$this->compileContainer($container);
40+
41+
$this->assertSame('custom_secret', $container->getDefinition('ux.live_component.component_hydrator')->getArgument(4));
42+
$this->assertSame('custom_secret', $container->getDefinition('ux.live_component.fingerprint_calculator')->getArgument(0));
43+
}
44+
45+
private function createContainer(): ContainerBuilder
46+
{
47+
$container = new ContainerBuilder(new ParameterBag([
48+
'kernel.cache_dir' => __DIR__,
49+
'kernel.project_dir' => __DIR__,
50+
'kernel.charset' => 'UTF-8',
51+
'kernel.debug' => false,
52+
'kernel.bundles' => [],
53+
'kernel.bundles_metadata' => [],
54+
]));
55+
56+
return $container;
57+
}
58+
59+
private function compileContainer(ContainerBuilder $container): void
60+
{
61+
$container->getCompilerPassConfig()->setOptimizationPasses([]);
62+
$container->getCompilerPassConfig()->setRemovingPasses([]);
63+
$container->getCompilerPassConfig()->setAfterRemovingPasses([]);
64+
$container->compile();
65+
}
66+
}

0 commit comments

Comments
 (0)