Repository: symfony/ux
Pull request #2461 (2024-12-21 16:49:15)

[LiveComponent] Check secret is not empty + add [SensitiveParameter]
| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Issues        | Fix #...
| License       | MIT

Improve security before we allow secret customization for LiveComponents (cf #2453)

I consider this a fix as passing an empty string for secret produces the same hash as passing null... which is deprecated for obvious reasons.



Patch:
diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php
index a5b23ce0..95f4bf91 100644
--- a/src/LiveComponent/src/LiveComponentHydrator.php
+++ b/src/LiveComponent/src/LiveComponentHydrator.php
@@ -50,8 +50,11 @@ final class LiveComponentHydrator
         private PropertyAccessorInterface $propertyAccessor,
         private LiveComponentMetadataFactory $liveComponentMetadataFactory,
         private NormalizerInterface|DenormalizerInterface|null $serializer,
-        private string $secret,
+        #[\SensitiveParameter] private string $secret,
     ) {
+        if (!$secret) {
+            throw new \InvalidArgumentException('A non-empty secret is required.');
+        }
     }
 
     public function dehydrate(object $component, ComponentAttributes $attributes, LiveComponentMetadata $componentMetadata): DehydratedProps
diff --git a/src/LiveComponent/src/Util/FingerprintCalculator.php b/src/LiveComponent/src/Util/FingerprintCalculator.php
index 0f3da0e9..9134e1ca 100644
--- a/src/LiveComponent/src/Util/FingerprintCalculator.php
+++ b/src/LiveComponent/src/Util/FingerprintCalculator.php
@@ -24,11 +24,14 @@ use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadata;
  *
  * @internal
  */
-class FingerprintCalculator
+final class FingerprintCalculator
 {
     public function __construct(
-        private string $secret,
+        #[\SensitiveParameter] private string $secret,
     ) {
+        if (!$secret) {
+            throw new \InvalidArgumentException('A non-empty secret is required.');
+        }
     }
 
     public function calculateFingerprint(array $inputProps, LiveComponentMetadata $liveMetadata): string


Test patch:
diff --git a/src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php
new file mode 100644
index 00000000..f14ff4a7
--- /dev/null
+++ b/src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\LiveComponent\Tests\Unit;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
+use Symfony\UX\LiveComponent\LiveComponentHydrator;
+use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
+
+final class LiveComponentHydratorTest extends TestCase
+{
+    public function testConstructWithEmptySecret(): void
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('A non-empty secret is required.');
+
+        new LiveComponentHydrator(
+            [],
+            $this->createMock(PropertyAccessorInterface::class),
+            $this->createMock(LiveComponentMetadataFactory::class),
+            $this->createMock(NormalizerInterface::class),
+            '',
+        );
+    }
+}
diff --git a/src/LiveComponent/tests/Unit/Util/FingerprintCalculatorTest.php b/src/LiveComponent/tests/Unit/Util/FingerprintCalculatorTest.php
new file mode 100644
index 00000000..71f115d9
--- /dev/null
+++ b/src/LiveComponent/tests/Unit/Util/FingerprintCalculatorTest.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\LiveComponent\Tests\Unit\Util;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\UX\LiveComponent\Util\FingerprintCalculator;
+
+final class FingerprintCalculatorTest extends TestCase
+{
+    public function testConstructWithEmptySecret(): void
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('A non-empty secret is required.');
+
+        new FingerprintCalculator('');
+    }
+}