diff --git a/UPGRADE-5.4.md b/UPGRADE-5.4.md index 2cccd102addfe..cab9c568ea383 100644 --- a/UPGRADE-5.4.md +++ b/UPGRADE-5.4.md @@ -38,6 +38,8 @@ Messenger SecurityBundle -------------- + * Deprecate `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, the logic is moved into the + `HttpBasicAuthenticator` and `ChannelListener` respectively * Deprecate not setting `$authenticatorManagerEnabled` to `true` in `SecurityDataCollector` and `DebugFirewallCommand` * Deprecate `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of `AuthenticatorFactoryInterface` and `SecurityExtension::addAuthenticatorFactory()` @@ -59,6 +61,10 @@ SecurityBundle Security -------- + * Deprecate the `$authenticationEntryPoint` argument of `ChannelListener`, and add `$httpPort` and `$httpsPort` arguments + * Deprecate `RetryAuthenticationEntryPoint`, this code is now inlined in the `ChannelListener` + * Deprecate `FormAuthenticationEntryPoint` and `BasicAuthenticationEntryPoint`, in the new system the `FormLoginAuthenticator` + and `HttpBasicAuthenticator` should be used instead * Deprecate `AnonymousToken`, as the related authenticator was deprecated in 5.3 * Deprecate `Token::getCredentials()`, tokens should no longer contain credentials (as they represent authenticated sessions) * Deprecate not returning an `UserInterface` from `Token::getUser()` diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 9fd237c2def19..bb1ab38e87588 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -207,6 +207,9 @@ Routing Security -------- + * Remove the `$authenticationEntryPoint` argument of `ChannelListener` + * Remove `RetryAuthenticationEntryPoint`, this code was inlined in the `ChannelListener` + * Remove `FormAuthenticationEntryPoint` and `BasicAuthenticationEntryPoint`, the `FormLoginAuthenticator` and `HttpBasicAuthenticator` should be used instead. * Remove `AnonymousToken` * Remove `Token::getCredentials()`, tokens should no longer contain credentials (as they represent authenticated sessions) * Restrict the return type of `Token::getUser()` to `UserInterface` (removing `string|\Stringable`) @@ -383,6 +386,8 @@ Security SecurityBundle -------------- + * Remove `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, + the logic is moved into the `HttpBasicAuthenticator` and `ChannelListener` respectively * Remove `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of `AuthenticatorFactoryInterface` and `SecurityExtension::addAuthenticatorFactory()` * Add `AuthenticatorFactoryInterface::getPriority()` which replaces `SecurityFactoryInterface::getPosition()`. diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 7f661ddb085ed..f7d2c70d94996 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 5.4 --- + * Deprecate `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, the logic is moved into the + `HttpBasicAuthenticator` and `ChannelListener` respectively * Deprecate `FirewallConfig::allowsAnonymous()` and the `allows_anonymous` from the data collector data, there will be no anonymous concept as of version 6. * Deprecate not setting `$authenticatorManagerEnabled` to `true` in `SecurityDataCollector` and `DebugFirewallCommand` * Deprecate `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php index 163e6a63ca041..72129d1bbf865 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php @@ -32,19 +32,22 @@ return static function (ContainerConfigurator $container) { $container->services() + ->set('security.authentication.basic_entry_point', BasicAuthenticationEntryPoint::class) + ->deprecate('symfony/security-bundle', '5.4', 'The "%service_id%" service is deprecated, the logic is contained in the authenticators.') + ->set('security.authentication.retry_entry_point', RetryAuthenticationEntryPoint::class) + ->deprecate('symfony/security-bundle', '5.4', 'The "%service_id%" service is deprecated, the logic is integrated directly in "security.channel_listener".') ->args([ inline_service('int')->factory([service('router.request_context'), 'getHttpPort']), inline_service('int')->factory([service('router.request_context'), 'getHttpsPort']), ]) - ->set('security.authentication.basic_entry_point', BasicAuthenticationEntryPoint::class) - ->set('security.channel_listener', ChannelListener::class) ->args([ service('security.access_map'), - service('security.authentication.retry_entry_point'), service('logger')->nullOnInvalid(), + inline_service('int')->factory([service('router.request_context'), 'getHttpPort']), + inline_service('int')->factory([service('router.request_context'), 'getHttpsPort']), ]) ->tag('monolog.logger', ['channel' => 'security']) diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md index 26c375a44dc8a..597a71b397762 100644 --- a/src/Symfony/Component/Security/Http/CHANGELOG.md +++ b/src/Symfony/Component/Security/Http/CHANGELOG.md @@ -4,6 +4,10 @@ CHANGELOG 5.4 --- + * Deprecate the `$authenticationEntryPoint` argument of `ChannelListener`, and add `$httpPort` and `$httpsPort` arguments + * Deprecate `RetryAuthenticationEntryPoint`, this code is now inlined in the `ChannelListener` + * Deprecate `FormAuthenticationEntryPoint` and `BasicAuthenticationEntryPoint`, in the new system the `FormLoginAuthenticator` + and `HttpBasicAuthenticator` should be used instead * Deprecate the `$authManager` argument of `AccessListener` * Deprecate not setting the `$exceptionOnNoToken` argument of `AccessListener` to `false` * Deprecate `DeauthenticatedEvent`, use `TokenDeauthenticatedEvent` instead diff --git a/src/Symfony/Component/Security/Http/EntryPoint/BasicAuthenticationEntryPoint.php b/src/Symfony/Component/Security/Http/EntryPoint/BasicAuthenticationEntryPoint.php index b75d0ebd3274b..53a029360b79d 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/BasicAuthenticationEntryPoint.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/BasicAuthenticationEntryPoint.php @@ -14,11 +14,16 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator; + +trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use the new security system with "%s" instead.', BasicAuthenticationEntryPoint::class, HttpBasicAuthenticator::class); /** * BasicAuthenticationEntryPoint starts an HTTP Basic authentication. * * @author Fabien Potencier + * + * @deprecated since Symfony 5.4 */ class BasicAuthenticationEntryPoint implements AuthenticationEntryPointInterface { diff --git a/src/Symfony/Component/Security/Http/EntryPoint/FormAuthenticationEntryPoint.php b/src/Symfony/Component/Security/Http/EntryPoint/FormAuthenticationEntryPoint.php index c887ca44b1856..32cc5a0e06db0 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/FormAuthenticationEntryPoint.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/FormAuthenticationEntryPoint.php @@ -14,12 +14,17 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator; use Symfony\Component\Security\Http\HttpUtils; +trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use the new security system with "%s" instead.', FormAuthenticationEntryPoint::class, FormLoginAuthenticator::class); + /** * FormAuthenticationEntryPoint starts an authentication via a login form. * * @author Fabien Potencier + * + * @deprecated since Symfony 5.4 */ class FormAuthenticationEntryPoint implements AuthenticationEntryPointInterface { diff --git a/src/Symfony/Component/Security/Http/EntryPoint/RetryAuthenticationEntryPoint.php b/src/Symfony/Component/Security/Http/EntryPoint/RetryAuthenticationEntryPoint.php index ca7f9121ee1b8..55e86f96d6f4b 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/RetryAuthenticationEntryPoint.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/RetryAuthenticationEntryPoint.php @@ -14,6 +14,9 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Firewall\ChannelListener; + +trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" directly (and optionally configure the HTTP(s) ports there).', RetryAuthenticationEntryPoint::class, ChannelListener::class); /** * RetryAuthenticationEntryPoint redirects URL based on the configured scheme. @@ -21,6 +24,8 @@ * This entry point is not intended to work with HTTP post requests. * * @author Fabien Potencier + * + * @deprecated since Symfony 5.4 */ class RetryAuthenticationEntryPoint implements AuthenticationEntryPointInterface { diff --git a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php index 4c5f1f09b60cf..67bb2ae337f82 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Http\Firewall; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Http\AccessMapInterface; @@ -28,14 +29,31 @@ class ChannelListener extends AbstractListener { private $map; - private $authenticationEntryPoint; + private $authenticationEntryPoint = null; private $logger; + private $httpPort; + private $httpsPort; - public function __construct(AccessMapInterface $map, AuthenticationEntryPointInterface $authenticationEntryPoint, LoggerInterface $logger = null) + public function __construct(AccessMapInterface $map, /*LoggerInterface*/ $logger = null, /*int*/ $httpPort = 80, /*int*/ $httpsPort = 443) { + if ($logger instanceof AuthenticationEntryPointInterface) { + trigger_deprecation('symfony/security-http', '5.4', 'The "$authenticationEntryPoint" argument of "%s()" is deprecated.', __METHOD__); + + $this->authenticationEntryPoint = $logger; + $nrOfArgs = \func_num_args(); + $logger = $nrOfArgs > 2 ? func_get_arg(2) : null; + $httpPort = $nrOfArgs > 3 ? func_get_arg(3) : 80; + $httpPort = $nrOfArgs > 4 ? func_get_arg(4) : 443; + } + + if (null !== $logger && !$logger instanceof LoggerInterface) { + throw new \TypeError(sprintf('Argument "$logger" of "%s()" must be instance of "%s", "%s" given.', __METHOD__, LoggerInterface::class, get_debug_type($logger))); + } + $this->map = $map; - $this->authenticationEntryPoint = $authenticationEntryPoint; $this->logger = $logger; + $this->httpPort = $httpPort; + $this->httpsPort = $httpsPort; } /** @@ -74,8 +92,31 @@ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); - $response = $this->authenticationEntryPoint->start($request); + $event->setResponse($this->createRedirectResponse($request)); + } + + private function createRedirectResponse(Request $request): RedirectResponse + { + if (null !== $this->authenticationEntryPoint) { + return $this->authenticationEntryPoint->start($request); + } + + $scheme = $request->isSecure() ? 'http' : 'https'; + if ('http' === $scheme && 80 != $this->httpPort) { + $port = ':'.$this->httpPort; + } elseif ('https' === $scheme && 443 != $this->httpsPort) { + $port = ':'.$this->httpsPort; + } else { + $port = ''; + } + + $qs = $request->getQueryString(); + if (null !== $qs) { + $qs = '?'.$qs; + } + + $url = $scheme.'://'.$request->getHost().$port.$request->getBaseUrl().$request->getPathInfo().$qs; - $event->setResponse($response); + return new RedirectResponse($url, 301); } } diff --git a/src/Symfony/Component/Security/Http/Tests/EntryPoint/BasicAuthenticationEntryPointTest.php b/src/Symfony/Component/Security/Http/Tests/EntryPoint/BasicAuthenticationEntryPointTest.php index 0d17b5c8bbd88..5bf711235a7da 100644 --- a/src/Symfony/Component/Security/Http/Tests/EntryPoint/BasicAuthenticationEntryPointTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EntryPoint/BasicAuthenticationEntryPointTest.php @@ -16,6 +16,9 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint; +/** + * @group legacy + */ class BasicAuthenticationEntryPointTest extends TestCase { public function testStart() diff --git a/src/Symfony/Component/Security/Http/Tests/EntryPoint/FormAuthenticationEntryPointTest.php b/src/Symfony/Component/Security/Http/Tests/EntryPoint/FormAuthenticationEntryPointTest.php index 462607a46a1ef..565201736d3d4 100644 --- a/src/Symfony/Component/Security/Http/Tests/EntryPoint/FormAuthenticationEntryPointTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EntryPoint/FormAuthenticationEntryPointTest.php @@ -19,6 +19,9 @@ use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint; use Symfony\Component\Security\Http\HttpUtils; +/** + * @group legacy + */ class FormAuthenticationEntryPointTest extends TestCase { public function testStart() diff --git a/src/Symfony/Component/Security/Http/Tests/EntryPoint/RetryAuthenticationEntryPointTest.php b/src/Symfony/Component/Security/Http/Tests/EntryPoint/RetryAuthenticationEntryPointTest.php index 13dff28fcebcc..e9e5ddd54aba4 100644 --- a/src/Symfony/Component/Security/Http/Tests/EntryPoint/RetryAuthenticationEntryPointTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EntryPoint/RetryAuthenticationEntryPointTest.php @@ -16,6 +16,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint; +/** + * @group legacy + */ class RetryAuthenticationEntryPointTest extends TestCase { /** diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php index d38a03e87fbdb..ee025f55cbac0 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php @@ -12,10 +12,9 @@ namespace Symfony\Component\Security\Http\Tests\Firewall; use PHPUnit\Framework\TestCase; -use Psr\Log\NullLogger; use Symfony\Component\HttpFoundation\HeaderBag; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Http\AccessMapInterface; @@ -41,15 +40,9 @@ public function testHandleWithNotSecuredRequestAndHttpChannel() ->willReturn([[], 'http']) ; - $entryPoint = $this->createMock(AuthenticationEntryPointInterface::class); - $entryPoint - ->expects($this->never()) - ->method('start') - ; - $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST); - $listener = new ChannelListener($accessMap, $entryPoint); + $listener = new ChannelListener($accessMap); $listener($event); $this->assertNull($event->getResponse()); @@ -72,15 +65,9 @@ public function testHandleWithSecuredRequestAndHttpsChannel() ->willReturn([[], 'https']) ; - $entryPoint = $this->createMock(AuthenticationEntryPointInterface::class); - $entryPoint - ->expects($this->never()) - ->method('start') - ; - $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST); - $listener = new ChannelListener($accessMap, $entryPoint); + $listener = new ChannelListener($accessMap); $listener($event); $this->assertNull($event->getResponse()); @@ -95,8 +82,6 @@ public function testHandleWithNotSecuredRequestAndHttpsChannel() ->willReturn(false) ; - $response = new Response(); - $accessMap = $this->createMock(AccessMapInterface::class); $accessMap ->expects($this->any()) @@ -105,20 +90,14 @@ public function testHandleWithNotSecuredRequestAndHttpsChannel() ->willReturn([[], 'https']) ; - $entryPoint = $this->createMock(AuthenticationEntryPointInterface::class); - $entryPoint - ->expects($this->once()) - ->method('start') - ->with($this->equalTo($request)) - ->willReturn($response) - ; - $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST); - $listener = new ChannelListener($accessMap, $entryPoint); + $listener = new ChannelListener($accessMap); $listener($event); - $this->assertSame($response, $event->getResponse()); + $response = $event->getResponse(); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals('https://', $response->getTargetUrl()); } public function testHandleWithSecuredRequestAndHttpChannel() @@ -130,8 +109,6 @@ public function testHandleWithSecuredRequestAndHttpChannel() ->willReturn(true) ; - $response = new Response(); - $accessMap = $this->createMock(AccessMapInterface::class); $accessMap ->expects($this->any()) @@ -140,20 +117,14 @@ public function testHandleWithSecuredRequestAndHttpChannel() ->willReturn([[], 'http']) ; - $entryPoint = $this->createMock(AuthenticationEntryPointInterface::class); - $entryPoint - ->expects($this->once()) - ->method('start') - ->with($this->equalTo($request)) - ->willReturn($response) - ; - $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST); - $listener = new ChannelListener($accessMap, $entryPoint); + $listener = new ChannelListener($accessMap); $listener($event); - $this->assertSame($response, $event->getResponse()); + $response = $event->getResponse(); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals('http://', $response->getTargetUrl()); } public function testSupportsWithoutHeaders() @@ -174,10 +145,46 @@ public function testSupportsWithoutHeaders() ->willReturn([[], 'https']) ; + $listener = new ChannelListener($accessMap); + + $this->assertTrue($listener->supports($request)); + } + + /** + * @group legacy + */ + public function testLegacyHandleWithEntryPoint() + { + $request = $this->createMock(Request::class); + $request + ->expects($this->any()) + ->method('isSecure') + ->willReturn(false) + ; + + $accessMap = $this->createMock(AccessMapInterface::class); + $accessMap + ->expects($this->any()) + ->method('getPatterns') + ->with($this->equalTo($request)) + ->willReturn([[], 'https']) + ; + + $response = new RedirectResponse('/redirected'); + $entryPoint = $this->createMock(AuthenticationEntryPointInterface::class); + $entryPoint + ->expects($this->once()) + ->method('start') + ->with($this->equalTo($request)) + ->willReturn($response) + ; - $listener = new ChannelListener($accessMap, $entryPoint, new NullLogger()); + $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST); - $this->assertTrue($listener->supports($request)); + $listener = new ChannelListener($accessMap, $entryPoint); + $listener($event); + + $this->assertSame($response, $event->getResponse()); } }