diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9f64932..e843287 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -30,6 +30,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Security + +- **`allow_unauthenticated` refused in production.** The switch opens the layout admin to anonymous users; the + container now refuses to compile with it in the `prod` environment or in any non-debug kernel outside `test*` + environments (as ContactFormBundle). + ## [1.8.1] - 2026-10-28 Security: the admin controllers are guarded by class, so a route name_prefix no longer bypasses the access check. See [UPGRADING](UPGRADING.md#to-181). diff --git a/src/DependencyInjection/NowoPageLayoutKitExtension.php b/src/DependencyInjection/NowoPageLayoutKitExtension.php index ac69d3b..32377f8 100644 --- a/src/DependencyInjection/NowoPageLayoutKitExtension.php +++ b/src/DependencyInjection/NowoPageLayoutKitExtension.php @@ -25,6 +25,7 @@ use function array_key_exists; use function is_array; use function is_string; +use function str_starts_with; /** * Loads PageLayoutKit services and publishes configuration parameters. @@ -117,6 +118,7 @@ public function getAlias(): string private function registerAccessChecker(ContainerBuilder $container, array $security): void { if ($security['allow_unauthenticated']) { + $this->refuseAllowUnauthenticatedInProduction($container); $id = 'nowo_page_layout_kit.access_checker.allow_all'; $container->setDefinition($id, new Definition(AllowAllPageLayoutKitAccessChecker::class)); $container->setAlias(PageLayoutKitAccessCheckerInterface::class, $id); @@ -194,4 +196,19 @@ private function isSecurityBundleAvailable(ContainerBuilder $container): bool return isset($bundles['SecurityBundle']); } + + /** + * `allow_unauthenticated` opens the layout admin to anonymous users: a dev / demo switch, refused in the prod + * environment and in any non-debug kernel outside the test environments (test*), as in ContactFormBundle. + */ + private function refuseAllowUnauthenticatedInProduction(ContainerBuilder $container): void + { + $environment = $container->hasParameter('kernel.environment') ? $container->getParameter('kernel.environment') : ''; + $environment = is_string($environment) ? $environment : ''; + $debug = $container->hasParameter('kernel.debug') ? (bool) $container->getParameter('kernel.debug') : true; + + if ($environment === 'prod' || (!$debug && !str_starts_with($environment, 'test'))) { + throw new LogicException('nowo_page_layout_kit.security.allow_unauthenticated is a dev/demo switch: it opens the layout admin to anonymous users and is refused in the prod environment and in non-debug kernels (test* environments excepted).'); + } + } } diff --git a/tests/Unit/DependencyInjection/NowoPageLayoutKitExtensionTest.php b/tests/Unit/DependencyInjection/NowoPageLayoutKitExtensionTest.php index 0c9fd50..72a0365 100644 --- a/tests/Unit/DependencyInjection/NowoPageLayoutKitExtensionTest.php +++ b/tests/Unit/DependencyInjection/NowoPageLayoutKitExtensionTest.php @@ -20,6 +20,7 @@ use Nowo\PageLayoutKitBundle\Security\PageLayoutProtection; use Nowo\PageLayoutKitBundle\Security\PageLayoutProtectionConfig; use Nowo\PageLayoutKitBundle\Service\PageBlockMigrator; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -98,6 +99,33 @@ public function testPrependPublishesTheDefaultFormKitProfileWhenMissing(): void (new NowoPageLayoutKitExtension())->prepend($container); } + /** + * @return iterable + */ + public static function allowUnauthenticatedKernels(): iterable + { + yield 'dev debug' => ['dev', true, true]; + yield 'test without debug' => ['test', false, true]; + yield 'prod' => ['prod', true, false]; + yield 'staging without debug' => ['staging', false, false]; + } + + #[DataProvider('allowUnauthenticatedKernels')] + public function testAllowUnauthenticatedIsRefusedInProduction(string $environment, bool $debug, bool $allowed): void + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.environment', $environment); + $container->setParameter('kernel.debug', $debug); + if (!$allowed) { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('dev/demo switch'); + } + + (new NowoPageLayoutKitExtension())->load([['security' => ['allow_unauthenticated' => true]]], $container); + + self::assertTrue($container->hasDefinition('nowo_page_layout_kit.access_checker.allow_all')); + } + public function testLoadPublishesParametersAndRegistersAllowAllAccessChecker(): void { $container = new ContainerBuilder();