diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 9cc22375c7aaf..3d030f6ddfd35 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Allow an array of `pattern` in firewall configuration * Add `$badges` argument to `Security::login` * Deprecate the `require_previous_session` config option. Setting it has no effect anymore + * Add `LogoutRouteLoader` 6.3 --- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index cb10cf4b5c69c..58ab6d1cae41a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -49,6 +49,7 @@ use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; +use Symfony\Component\Routing\Loader\ContainerLoader; use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; use Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy; use Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy; @@ -170,6 +171,13 @@ public function load(array $configs, ContainerBuilder $container) } $this->createFirewalls($config, $container); + + if ($container::willBeAvailable('symfony/routing', ContainerLoader::class, ['symfony/security-bundle'])) { + $this->createLogoutUrisParameter($config['firewalls'] ?? [], $container); + } else { + $container->removeDefinition('security.route_loader.logout'); + } + $this->createAuthorization($config, $container); $this->createRoleHierarchy($config, $container); @@ -1095,4 +1103,20 @@ private function getSortedFactories(): array return $this->sortedFactories; } + + private function createLogoutUrisParameter(array $firewallsConfig, ContainerBuilder $container): void + { + $logoutUris = []; + foreach ($firewallsConfig as $name => $config) { + if (!$logoutPath = $config['logout']['path'] ?? null) { + continue; + } + + if ('/' === $logoutPath[0]) { + $logoutUris[$name] = $logoutPath; + } + } + + $container->setParameter('security.logout_uris', $logoutUris); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php index 27cc0ce51e9c3..d17254892215c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php @@ -13,6 +13,7 @@ use Symfony\Bundle\SecurityBundle\CacheWarmer\ExpressionCacheWarmer; use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; +use Symfony\Bundle\SecurityBundle\Routing\LogoutRouteLoader; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; use Symfony\Bundle\SecurityBundle\Security\FirewallContext; @@ -229,6 +230,13 @@ service('security.token_storage')->nullOnInvalid(), ]) + ->set('security.route_loader.logout', LogoutRouteLoader::class) + ->args([ + '%security.logout_uris%', + 'security.logout_uris', + ]) + ->tag('routing.route_loader') + // Provisioning ->set('security.user.provider.missing', MissingUserProvider::class) ->abstract() diff --git a/src/Symfony/Bundle/SecurityBundle/Routing/LogoutRouteLoader.php b/src/Symfony/Bundle/SecurityBundle/Routing/LogoutRouteLoader.php new file mode 100644 index 0000000000000..e97b31c1a4621 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Routing/LogoutRouteLoader.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Routing; + +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +final class LogoutRouteLoader +{ + /** + * @param array $logoutUris Logout URIs indexed by the corresponding firewall name + * @param string $parameterName Name of the container parameter containing {@see $logoutUris}' value + */ + public function __construct( + private readonly array $logoutUris, + private readonly string $parameterName, + ) { + } + + public function __invoke(): RouteCollection + { + $collection = new RouteCollection(); + $collection->addResource(new ContainerParametersResource([$this->parameterName => $this->logoutUris])); + + $routeNames = []; + foreach ($this->logoutUris as $firewallName => $logoutPath) { + $routeName = '_logout_'.$firewallName; + + if (isset($routeNames[$logoutPath])) { + $collection->addAlias($routeName, $routeNames[$logoutPath]); + } else { + $routeNames[$logoutPath] = $routeName; + $collection->add($routeName, new Route($logoutPath)); + } + } + + return $collection; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Routing/LogoutRouteLoaderTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Routing/LogoutRouteLoaderTest.php new file mode 100644 index 0000000000000..5080f52fa7e6d --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Routing/LogoutRouteLoaderTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Routing; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\Routing\LogoutRouteLoader; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class LogoutRouteLoaderTest extends TestCase +{ + public function testLoad() + { + $logoutPaths = [ + 'main' => '/logout', + 'admin' => '/logout', + ]; + + $loader = new LogoutRouteLoader($logoutPaths, 'parameterName'); + $collection = $loader(); + + self::assertInstanceOf(RouteCollection::class, $collection); + self::assertCount(1, $collection); + self::assertEquals(new Route('/logout'), $collection->get('_logout_main')); + self::assertCount(1, $collection->getAliases()); + self::assertEquals('_logout_main', $collection->getAlias('_logout_admin')->getId()); + + $resources = $collection->getResources(); + self::assertCount(1, $resources); + + $resource = reset($resources); + self::assertInstanceOf(ContainerParametersResource::class, $resource); + self::assertSame(['parameterName' => $logoutPaths], $resource->getParameters()); + } +}