diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index 20f38b3422f1f..2f51e38874c8f 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,28 @@ in 4.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1 +* 4.4.10 (2020-06-12) + + * bug #37227 [DependencyInjection][CheckTypeDeclarationsPass] Handle unresolved parameters pointing to environment variables (fancyweb) + * bug #37103 [Form] switch the context when validating nested forms (xabbuh) + * bug #37182 [HttpKernel] Fix regression where Store does not return response body correctly (mpdude) + * bug #37193 [DependencyInjection][CheckTypeDeclarationsPass] Always resolve parameters (fancyweb) + * bug #37191 [HttpClient] fix offset computation for data chunks (nicolas-grekas) + * bug #37177 [Ldap] fix refreshUser() ignoring extra_fields (arkste) + * bug #37181 [Mailer] Remove an internal annot (fabpot) + * bug #36913 [FrameworkBundle] fix type annotation on ControllerTrait::addFlash() (ThomasLandauer) + * bug #37162 [Mailer] added the reply-to addresses to the API SES transport request. (ribeiropaulor) + * bug #37167 [Mime] use fromString when creating a new Address (fabpot) + * bug #37169 [Cache] fix forward compatibility with Doctrine DBAL 3 (xabbuh) + * bug #37159 [Mailer] Fixed generator bug when creating multiple transports using Transport::fromDsn (atailouloute) + * bug #37048 [HttpClient] fix monitoring timeouts when other streams are active (nicolas-grekas) + * bug #37085 [Form] properly cascade validation to child forms (xabbuh) + * bug #37095 [PhpUnitBridge] Fix undefined index when output of "composer show" cannot be parsed (nicolas-grekas) + * bug #37092 [PhpUnitBridge] fix undefined var on version 3.4 (nicolas-grekas) + * bug #37065 [HttpClient] Throw JsonException instead of TransportException on empty response in Response::toArray() (jeroennoten) + * bug #37077 [WebProfilerBundle] Move ajax clear event listener initialization on loadToolbar (Bruno BOUTAREL) + * bug #37049 [Serializer] take into account the context when preserving empty array objects (xabbuh) + * 4.4.9 (2020-05-31) * bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj) diff --git a/CHANGELOG-5.0.md b/CHANGELOG-5.0.md index cbd366b3c7393..1836adff61c2d 100644 --- a/CHANGELOG-5.0.md +++ b/CHANGELOG-5.0.md @@ -7,6 +7,28 @@ in 5.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.0.0...v5.0.1 +* 5.0.10 (2020-06-12) + + * bug #37227 [DependencyInjection][CheckTypeDeclarationsPass] Handle unresolved parameters pointing to environment variables (fancyweb) + * bug #37103 [Form] switch the context when validating nested forms (xabbuh) + * bug #37182 [HttpKernel] Fix regression where Store does not return response body correctly (mpdude) + * bug #37193 [DependencyInjection][CheckTypeDeclarationsPass] Always resolve parameters (fancyweb) + * bug #37191 [HttpClient] fix offset computation for data chunks (nicolas-grekas) + * bug #37177 [Ldap] fix refreshUser() ignoring extra_fields (arkste) + * bug #37181 [Mailer] Remove an internal annot (fabpot) + * bug #36913 [FrameworkBundle] fix type annotation on ControllerTrait::addFlash() (ThomasLandauer) + * bug #37162 [Mailer] added the reply-to addresses to the API SES transport request. (ribeiropaulor) + * bug #37167 [Mime] use fromString when creating a new Address (fabpot) + * bug #37169 [Cache] fix forward compatibility with Doctrine DBAL 3 (xabbuh) + * bug #37159 [Mailer] Fixed generator bug when creating multiple transports using Transport::fromDsn (atailouloute) + * bug #37048 [HttpClient] fix monitoring timeouts when other streams are active (nicolas-grekas) + * bug #37085 [Form] properly cascade validation to child forms (xabbuh) + * bug #37095 [PhpUnitBridge] Fix undefined index when output of "composer show" cannot be parsed (nicolas-grekas) + * bug #37092 [PhpUnitBridge] fix undefined var on version 3.4 (nicolas-grekas) + * bug #37065 [HttpClient] Throw JsonException instead of TransportException on empty response in Response::toArray() (jeroennoten) + * bug #37077 [WebProfilerBundle] Move ajax clear event listener initialization on loadToolbar (Bruno BOUTAREL) + * bug #37049 [Serializer] take into account the context when preserving empty array objects (xabbuh) + * 5.0.9 (2020-05-31) * bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj) diff --git a/CHANGELOG-5.1.md b/CHANGELOG-5.1.md index 4f34853bc8b91..54abc070fac2a 100644 --- a/CHANGELOG-5.1.md +++ b/CHANGELOG-5.1.md @@ -7,6 +7,16 @@ in 5.1 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.1.0...v5.1.1 +* 5.1.2 (2020-06-15) + + * bug #37265 [HttpFoundation] use InputBag for Request::$request only if data is coming from a form (nicolas-grekas) + * bug #37283 [SecurityBundle] Fix CookieClearingLogoutListener DI configuration (wouterj) + * bug #37160 Reset question validator attempts only for actual stdin (ostrolucky) + * bug #36975 [PropertyInfo] Make PhpDocExtractor compatible with phpDocumentor v5 (DerManoMann) + * bug #37279 [Form] Fixed prototype block prefixes hierarchy of the CollectionType (yceruto) + * bug #37276 [Form] Fixed block prefixes hierarchy of the CollectionType (yceruto) + * bug #37261 Fix register csrf protection listener (Ne-Lexa) + * 5.1.1 (2020-06-12) * bug #37227 [DependencyInjection][CheckTypeDeclarationsPass] Handle unresolved parameters pointing to environment variables (fancyweb) diff --git a/composer.json b/composer.json index d6f5c79afa065..d8acf3888c1ae 100644 --- a/composer.json +++ b/composer.json @@ -128,7 +128,7 @@ "egulias/email-validator": "~1.2,>=1.2.8|~2.0", "symfony/phpunit-bridge": "^5.0.8", "symfony/security-acl": "~2.8|~3.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "twig/cssinliner-extra": "^2.12", "twig/inky-extra": "^2.12", "twig/markdown-extra": "^2.12" diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php index 5af1c49568ba6..ccab76679a32a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php @@ -27,29 +27,25 @@ class RegisterCsrfFeaturesPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->has('security.csrf.token_storage')) { - return; - } - $this->registerCsrfProtectionListener($container); $this->registerLogoutHandler($container); } private function registerCsrfProtectionListener(ContainerBuilder $container) { - if (!$container->has('security.authenticator.manager')) { + if (!$container->has('security.authenticator.manager') || !$container->has('security.csrf.token_manager')) { return; } $container->register('security.listener.csrf_protection', CsrfProtectionListener::class) - ->addArgument(new Reference('security.csrf.token_storage')) + ->addArgument(new Reference('security.csrf.token_manager')) ->addTag('kernel.event_subscriber') ->setPublic(false); } protected function registerLogoutHandler(ContainerBuilder $container) { - if (!$container->has('security.logout_listener')) { + if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) { return; } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index 10b503b6bf96e..c8e5d9d5a093f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -54,7 +54,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php index 643cb1c40d2eb..f9363e8290dc0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php @@ -33,6 +33,12 @@ public static function tearDownAfterClass(): void static::deleteTmpDir(); } + public function provideSecuritySystems() + { + yield [['enable_authenticator_manager' => true]]; + yield [['enable_authenticator_manager' => false]]; + } + protected static function deleteTmpDir() { if (!file_exists($dir = sys_get_temp_dir().'/'.static::getVarDir())) { @@ -61,9 +67,10 @@ protected static function createKernel(array $options = []): KernelInterface return new $class( static::getVarDir(), $options['test_case'], - isset($options['root_config']) ? $options['root_config'] : 'config.yml', - isset($options['environment']) ? $options['environment'] : strtolower(static::getVarDir().$options['test_case']), - isset($options['debug']) ? $options['debug'] : false + $options['root_config'] ?? 'config.yml', + $options['environment'] ?? strtolower(static::getVarDir().$options['test_case']), + $options['debug'] ?? false, + $options['enable_authenticator_manager'] ?? false ); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php index dcfd6f29e8fea..0e636a4e2f9ce 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php @@ -13,11 +13,20 @@ class AuthenticationCommencingTest extends AbstractWebTestCase { - public function testAuthenticationIsCommencingIfAccessDeniedExceptionIsWrapped() + /** + * @dataProvider provideClientOptions + */ + public function testAuthenticationIsCommencingIfAccessDeniedExceptionIsWrapped(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml']); + $client = $this->createClient($options); $client->request('GET', '/secure-but-not-covered-by-access-control'); $this->assertRedirect($client->getResponse(), '/login'); } + + public function provideClientOptions() + { + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'legacy_config.yml', 'enable_authenticator_manager' => false]]; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php index 51f56c220d33c..a917e66c572c9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php @@ -19,9 +19,12 @@ class ClearRememberMeTest extends AbstractWebTestCase { - public function testUserChangeClearsCookie() + /** + * @dataProvider provideClientOptions + */ + public function testUserChangeClearsCookie(array $options) { - $client = $this->createClient(['test_case' => 'ClearRememberMe', 'root_config' => 'config.yml']); + $client = $this->createClient($options); $client->request('POST', '/login', [ '_username' => 'johannes', @@ -36,6 +39,12 @@ public function testUserChangeClearsCookie() $this->assertRedirect($client->getResponse(), '/login'); $this->assertNull($cookieJar->get('REMEMBERME')); } + + public function provideClientOptions() + { + yield [['test_case' => 'ClearRememberMe', 'root_config' => 'config.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'ClearRememberMe', 'root_config' => 'legacy_config.yml', 'enable_authenticator_manager' => false]]; + } } class RememberMeFooController diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php index 5b2999fed0d2e..f252314b0c4c1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php @@ -14,11 +14,11 @@ class CsrfFormLoginTest extends AbstractWebTestCase { /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLoginAndLogoutWithCsrfTokens($config) + public function testFormLoginAndLogoutWithCsrfTokens($options) { - $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['user_login[username]'] = 'johannes'; @@ -44,13 +44,17 @@ public function testFormLoginAndLogoutWithCsrfTokens($config) } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLoginWithInvalidCsrfToken($config) + public function testFormLoginWithInvalidCsrfToken($options) { - $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $form = $client->request('GET', '/login')->selectButton('login')->form(); + if ($options['enable_authenticator_manager'] ?? false) { + $form['user_login[username]'] = 'johannes'; + $form['user_login[password]'] = 'test'; + } $form['user_login[_token]'] = ''; $client->submit($form); @@ -61,11 +65,11 @@ public function testFormLoginWithInvalidCsrfToken($config) } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLoginWithCustomTargetPath($config) + public function testFormLoginWithCustomTargetPath($options) { - $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['user_login[username]'] = 'johannes'; @@ -81,11 +85,11 @@ public function testFormLoginWithCustomTargetPath($config) } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLoginRedirectsToProtectedResourceAfterLogin($config) + public function testFormLoginRedirectsToProtectedResourceAfterLogin($options) { - $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $client->request('GET', '/protected-resource'); $this->assertRedirect($client->getResponse(), '/login'); @@ -101,11 +105,11 @@ public function testFormLoginRedirectsToProtectedResourceAfterLogin($config) $this->assertStringContainsString('You\'re browsing to path "/protected-resource".', $text); } - public function getConfigs() + public function provideClientOptions() { - return [ - ['config.yml'], - ['routes_as_path.yml'], - ]; + yield [['test_case' => 'CsrfFormLogin', 'root_config' => 'config.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'CsrfFormLogin', 'root_config' => 'legacy_config.yml', 'enable_authenticator_manager' => false]]; + yield [['test_case' => 'CsrfFormLogin', 'root_config' => 'routes_as_path.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'CsrfFormLogin', 'root_config' => 'legacy_routes_as_path.yml', 'enable_authenticator_manager' => false]]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php index 77011409cfaa4..91cccd1c46eaa 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php @@ -31,9 +31,12 @@ public function testItUsesTheConfiguredEntryPointWhenUsingUnknownCredentials() ); } - public function testItUsesTheConfiguredEntryPointFromTheExceptionListenerWithFormLoginAndNoCredentials() + /** + * @dataProvider provideSecuritySystems + */ + public function testItUsesTheConfiguredEntryPointFromTheExceptionListenerWithFormLoginAndNoCredentials(array $options) { - $client = $this->createClient(['test_case' => 'FirewallEntryPoint', 'root_config' => 'config_form_login.yml']); + $client = $this->createClient($options + ['test_case' => 'FirewallEntryPoint', 'root_config' => 'config_form_login.yml']); $client->request('GET', '/secure/resource'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php index 641ef0e519a1d..45d74fc72261f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php @@ -14,11 +14,11 @@ class FormLoginTest extends AbstractWebTestCase { /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLogin($config) + public function testFormLogin(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -33,11 +33,11 @@ public function testFormLogin($config) } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLogout($config) + public function testFormLogout(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -66,11 +66,11 @@ public function testFormLogout($config) } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLoginWithCustomTargetPath($config) + public function testFormLoginWithCustomTargetPath(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -86,11 +86,11 @@ public function testFormLoginWithCustomTargetPath($config) } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testFormLoginRedirectsToProtectedResourceAfterLogin($config) + public function testFormLoginRedirectsToProtectedResourceAfterLogin(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $client->request('GET', '/protected_resource'); $this->assertRedirect($client->getResponse(), '/login'); @@ -106,11 +106,11 @@ public function testFormLoginRedirectsToProtectedResourceAfterLogin($config) $this->assertStringContainsString('You\'re browsing to path "/protected_resource".', $text); } - public function getConfigs() + public function provideClientOptions() { - return [ - ['config.yml'], - ['routes_as_path.yml'], - ]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'legacy_config.yml', 'enable_authenticator_manager' => false]]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'routes_as_path.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'legacy_routes_as_path.yml', 'enable_authenticator_manager' => false]]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php index a69f5e591d1fa..20010349efe8b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php @@ -18,9 +18,12 @@ */ class JsonLoginTest extends AbstractWebTestCase { - public function testDefaultJsonLoginSuccess() + /** + * @dataProvider provideSecuritySystems + */ + public function testDefaultJsonLoginSuccess(array $options) { - $client = $this->createClient(['test_case' => 'JsonLogin', 'root_config' => 'config.yml']); + $client = $this->createClient($options + ['test_case' => 'JsonLogin', 'root_config' => 'config.yml']); $client->request('POST', '/chk', [], [], ['CONTENT_TYPE' => 'application/json'], '{"user": {"login": "dunglas", "password": "foo"}}'); $response = $client->getResponse(); @@ -29,9 +32,12 @@ public function testDefaultJsonLoginSuccess() $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); } - public function testDefaultJsonLoginFailure() + /** + * @dataProvider provideSecuritySystems + */ + public function testDefaultJsonLoginFailure(array $options) { - $client = $this->createClient(['test_case' => 'JsonLogin', 'root_config' => 'config.yml']); + $client = $this->createClient($options + ['test_case' => 'JsonLogin', 'root_config' => 'config.yml']); $client->request('POST', '/chk', [], [], ['CONTENT_TYPE' => 'application/json'], '{"user": {"login": "dunglas", "password": "bad"}}'); $response = $client->getResponse(); @@ -40,9 +46,12 @@ public function testDefaultJsonLoginFailure() $this->assertSame(['error' => 'Invalid credentials.'], json_decode($response->getContent(), true)); } - public function testCustomJsonLoginSuccess() + /** + * @dataProvider provideSecuritySystems + */ + public function testCustomJsonLoginSuccess(array $options) { - $client = $this->createClient(['test_case' => 'JsonLogin', 'root_config' => 'custom_handlers.yml']); + $client = $this->createClient($options + ['test_case' => 'JsonLogin', 'root_config' => 'custom_handlers.yml']); $client->request('POST', '/chk', [], [], ['CONTENT_TYPE' => 'application/json'], '{"user": {"login": "dunglas", "password": "foo"}}'); $response = $client->getResponse(); @@ -51,9 +60,12 @@ public function testCustomJsonLoginSuccess() $this->assertSame(['message' => 'Good game @dunglas!'], json_decode($response->getContent(), true)); } - public function testCustomJsonLoginFailure() + /** + * @dataProvider provideSecuritySystems + */ + public function testCustomJsonLoginFailure(array $options) { - $client = $this->createClient(['test_case' => 'JsonLogin', 'root_config' => 'custom_handlers.yml']); + $client = $this->createClient($options + ['test_case' => 'JsonLogin', 'root_config' => 'custom_handlers.yml']); $client->request('POST', '/chk', [], [], ['CONTENT_TYPE' => 'application/json'], '{"user": {"login": "dunglas", "password": "bad"}}'); $response = $client->getResponse(); @@ -62,9 +74,12 @@ public function testCustomJsonLoginFailure() $this->assertSame(['message' => 'Something went wrong'], json_decode($response->getContent(), true)); } - public function testDefaultJsonLoginBadRequest() + /** + * @dataProvider provideSecuritySystems + */ + public function testDefaultJsonLoginBadRequest(array $options) { - $client = $this->createClient(['test_case' => 'JsonLogin', 'root_config' => 'config.yml']); + $client = $this->createClient($options + ['test_case' => 'JsonLogin', 'root_config' => 'config.yml']); $client->request('POST', '/chk', [], [], ['CONTENT_TYPE' => 'application/json'], 'Not a json content'); $response = $client->getResponse(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php index b6d68fdd26b59..334c526580ba3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php @@ -14,11 +14,11 @@ class LocalizedRoutesAsPathTest extends AbstractWebTestCase { /** - * @dataProvider getLocales + * @dataProvider getLocalesAndClientConfig */ - public function testLoginLogoutProcedure($locale) + public function testLoginLogoutProcedure($locale, array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'localized_routes.yml']); + $client = $this->createClient(['test_case' => 'StandardFormLogin'] + $options); $crawler = $client->request('GET', '/'.$locale.'/login'); $form = $crawler->selectButton('login')->form(); @@ -36,11 +36,11 @@ public function testLoginLogoutProcedure($locale) /** * @group issue-32995 - * @dataProvider getLocales + * @dataProvider getLocalesAndClientConfig */ - public function testLoginFailureWithLocalizedFailurePath($locale) + public function testLoginFailureWithLocalizedFailurePath($locale, array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'localized_form_failure_handler.yml']); + $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => ($options['enable_authenticator_manager'] ? '' : 'legacy_').'localized_form_failure_handler.yml'] + $options); $crawler = $client->request('GET', '/'.$locale.'/login'); $form = $crawler->selectButton('login')->form(); @@ -52,29 +52,32 @@ public function testLoginFailureWithLocalizedFailurePath($locale) } /** - * @dataProvider getLocales + * @dataProvider getLocalesAndClientConfig */ - public function testAccessRestrictedResource($locale) + public function testAccessRestrictedResource($locale, array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'localized_routes.yml']); + $client = $this->createClient(['test_case' => 'StandardFormLogin'] + $options); $client->request('GET', '/'.$locale.'/secure/'); $this->assertRedirect($client->getResponse(), '/'.$locale.'/login'); } /** - * @dataProvider getLocales + * @dataProvider getLocalesAndClientConfig */ - public function testAccessRestrictedResourceWithForward($locale) + public function testAccessRestrictedResourceWithForward($locale, array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'localized_routes_with_forward.yml']); + $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'localized_routes_with_forward.yml'] + $options); $crawler = $client->request('GET', '/'.$locale.'/secure/'); $this->assertCount(1, $crawler->selectButton('login'), (string) $client->getResponse()); } - public function getLocales() + public function getLocalesAndClientConfig() { - return [['en'], ['de']]; + yield ['en', ['enable_authenticator_manager' => true, 'root_config' => 'localized_routes.yml']]; + yield ['en', ['enable_authenticator_manager' => false, 'root_config' => 'legacy_localized_routes.yml']]; + yield ['de', ['enable_authenticator_manager' => true, 'root_config' => 'localized_routes.yml']]; + yield ['de', ['enable_authenticator_manager' => false, 'root_config' => 'legacy_localized_routes.yml']]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php index cb7868f3256ef..b5e2b48487895 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php @@ -11,11 +11,16 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; +use Symfony\Component\BrowserKit\Cookie; + class LogoutTest extends AbstractWebTestCase { - public function testSessionLessRememberMeLogout() + /** + * @dataProvider provideSecuritySystems + */ + public function testSessionLessRememberMeLogout(array $options) { - $client = $this->createClient(['test_case' => 'RememberMeLogout', 'root_config' => 'config.yml']); + $client = $this->createClient($options + ['test_case' => 'RememberMeLogout', 'root_config' => 'config.yml']); $client->request('POST', '/login', [ '_username' => 'johannes', @@ -33,9 +38,12 @@ public function testSessionLessRememberMeLogout() $this->assertNull($cookieJar->get('REMEMBERME')); } - public function testCsrfTokensAreClearedOnLogout() + /** + * @dataProvider provideSecuritySystems + */ + public function testCsrfTokensAreClearedOnLogout(array $options) { - $client = $this->createClient(['test_case' => 'LogoutWithoutSessionInvalidation', 'root_config' => 'config.yml']); + $client = $this->createClient($options + ['test_case' => 'LogoutWithoutSessionInvalidation', 'root_config' => 'config.yml']); static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar'); $client->request('POST', '/login', [ @@ -51,13 +59,30 @@ public function testCsrfTokensAreClearedOnLogout() $this->assertFalse(static::$container->get('security.csrf.token_storage')->hasToken('foo')); } - public function testAccessControlDoesNotApplyOnLogout() + /** + * @dataProvider provideSecuritySystems + */ + public function testAccessControlDoesNotApplyOnLogout(array $options) { - $client = $this->createClient(['test_case' => 'LogoutAccess', 'root_config' => 'config.yml']); + $client = $this->createClient($options + ['test_case' => 'Logout', 'root_config' => 'config_access.yml']); + + $client->request('POST', '/login', ['_username' => 'johannes', '_password' => 'test']); + $client->request('GET', '/logout'); + + $this->assertRedirect($client->getResponse(), '/'); + } + + public function testCookieClearingOnLogout() + { + $client = $this->createClient(['test_case' => 'Logout', 'root_config' => 'config_cookie_clearing.yml']); + + $cookieJar = $client->getCookieJar(); + $cookieJar->set(new Cookie('flavor', 'chocolate', strtotime('+1 day'), null, 'somedomain')); $client->request('POST', '/login', ['_username' => 'johannes', '_password' => 'test']); $client->request('GET', '/logout'); $this->assertRedirect($client->getResponse(), '/'); + $this->assertNull($cookieJar->get('flavor')); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php index 0303f1b4eeff9..6bb05400b703f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php @@ -14,33 +14,33 @@ class SecurityRoutingIntegrationTest extends AbstractWebTestCase { /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testRoutingErrorIsNotExposedForProtectedResourceWhenAnonymous($config) + public function testRoutingErrorIsNotExposedForProtectedResourceWhenAnonymous(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $client->request('GET', '/protected_resource'); $this->assertRedirect($client->getResponse(), '/login'); } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testRoutingErrorIsExposedWhenNotProtected($config) + public function testRoutingErrorIsExposedWhenNotProtected(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $client->request('GET', '/unprotected_resource'); $this->assertEquals(404, $client->getResponse()->getStatusCode(), (string) $client->getResponse()); } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testRoutingErrorIsNotExposedForProtectedResourceWhenLoggedInWithInsufficientRights($config) + public function testRoutingErrorIsNotExposedForProtectedResourceWhenLoggedInWithInsufficientRights(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config]); + $client = $this->createClient($options); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -53,38 +53,38 @@ public function testRoutingErrorIsNotExposedForProtectedResourceWhenLoggedInWith } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testSecurityConfigurationForSingleIPAddress($config) + public function testSecurityConfigurationForSingleIPAddress(array $options) { - $allowedClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '10.10.10.10']); + $allowedClient = $this->createClient($options, ['REMOTE_ADDR' => '10.10.10.10']); $this->ensureKernelShutdown(); - $barredClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '10.10.20.10']); + $barredClient = $this->createClient($options, ['REMOTE_ADDR' => '10.10.20.10']); $this->assertAllowed($allowedClient, '/secured-by-one-ip'); $this->assertRestricted($barredClient, '/secured-by-one-ip'); } /** - * @dataProvider getConfigs + * @dataProvider provideClientOptions */ - public function testSecurityConfigurationForMultipleIPAddresses($config) + public function testSecurityConfigurationForMultipleIPAddresses(array $options) { - $allowedClientA = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '1.1.1.1']); + $allowedClientA = $this->createClient($options, ['REMOTE_ADDR' => '1.1.1.1']); $this->ensureKernelShutdown(); - $allowedClientB = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '2.2.2.2']); + $allowedClientB = $this->createClient($options, ['REMOTE_ADDR' => '2.2.2.2']); $this->ensureKernelShutdown(); - $allowedClientC = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '203.0.113.0']); + $allowedClientC = $this->createClient($options, ['REMOTE_ADDR' => '203.0.113.0']); $this->ensureKernelShutdown(); - $barredClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['REMOTE_ADDR' => '192.168.1.1']); + $barredClient = $this->createClient($options, ['REMOTE_ADDR' => '192.168.1.1']); $this->assertAllowed($allowedClientA, '/secured-by-two-ips'); $this->assertAllowed($allowedClientB, '/secured-by-two-ips'); @@ -97,19 +97,19 @@ public function testSecurityConfigurationForMultipleIPAddresses($config) } /** - * @dataProvider getConfigs + * @dataProvider provideConfigs */ - public function testSecurityConfigurationForExpression($config) + public function testSecurityConfigurationForExpression(array $options) { - $allowedClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], ['HTTP_USER_AGENT' => 'Firefox 1.0']); + $allowedClient = $this->createClient($options, ['HTTP_USER_AGENT' => 'Firefox 1.0']); $this->assertAllowed($allowedClient, '/protected-via-expression'); $this->ensureKernelShutdown(); - $barredClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], []); + $barredClient = $this->createClient($options, []); $this->assertRestricted($barredClient, '/protected-via-expression'); $this->ensureKernelShutdown(); - $allowedClient = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => $config], []); + $allowedClient = $this->createClient($options, []); $allowedClient->request('GET', '/protected-via-expression'); $form = $allowedClient->followRedirect()->selectButton('login')->form(); @@ -120,18 +120,24 @@ public function testSecurityConfigurationForExpression($config) $this->assertAllowed($allowedClient, '/protected-via-expression'); } - public function testInvalidIpsInAccessControl() + /** + * @dataProvider provideSecuritySystems + */ + public function testInvalidIpsInAccessControl(array $options) { $this->expectException(\LogicException::class); $this->expectExceptionMessage('The given value "256.357.458.559" in the "security.access_control" config option is not a valid IP address.'); - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'invalid_ip_access_control.yml']); + $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'invalid_ip_access_control.yml'] + $options); $client->request('GET', '/unprotected_resource'); } - public function testPublicHomepage() + /** + * @dataProvider provideSecuritySystems + */ + public function testPublicHomepage(array $options) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml']); + $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml'] + $options); $client->request('GET', '/en/'); $this->assertEquals(200, $client->getResponse()->getStatusCode(), (string) $client->getResponse()); @@ -151,8 +157,17 @@ private function assertRestricted($client, $path) $this->assertEquals(302, $client->getResponse()->getStatusCode()); } - public function getConfigs() + public function provideClientOptions() + { + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'legacy_config.yml', 'enable_authenticator_manager' => false]]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'routes_as_path.yml', 'enable_authenticator_manager' => true]]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'legacy_routes_as_path.yml', 'enable_authenticator_manager' => false]]; + } + + public function provideConfigs() { - return [['config.yml'], ['routes_as_path.yml']]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'legacy_config.yml']]; + yield [['test_case' => 'StandardFormLogin', 'root_config' => 'legacy_routes_as_path.yml']]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php index 183b1ad8c4ef8..194a1d7886dc6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php @@ -19,9 +19,9 @@ class SwitchUserTest extends AbstractWebTestCase /** * @dataProvider getTestParameters */ - public function testSwitchUser($originalUser, $targetUser, $expectedUser, $expectedStatus) + public function testSwitchUser($originalUser, $authenticatorManagerEnabled, $targetUser, $expectedUser, $expectedStatus) { - $client = $this->createAuthenticatedClient($originalUser); + $client = $this->createAuthenticatedClient($originalUser, ['enable_authenticator_manager' => $authenticatorManagerEnabled]); $client->request('GET', '/profile?_switch_user='.$targetUser); @@ -29,9 +29,12 @@ public function testSwitchUser($originalUser, $targetUser, $expectedUser, $expec $this->assertEquals($expectedUser, $client->getProfile()->getCollector('security')->getUser()); } - public function testSwitchedUserCanSwitchToOther() + /** + * @dataProvider provideSecuritySystems + */ + public function testSwitchedUserCanSwitchToOther(array $options) { - $client = $this->createAuthenticatedClient('user_can_switch'); + $client = $this->createAuthenticatedClient('user_can_switch', $options); $client->request('GET', '/profile?_switch_user=user_cannot_switch_1'); $client->request('GET', '/profile?_switch_user=user_cannot_switch_2'); @@ -40,9 +43,12 @@ public function testSwitchedUserCanSwitchToOther() $this->assertEquals('user_cannot_switch_2', $client->getProfile()->getCollector('security')->getUser()); } - public function testSwitchedUserExit() + /** + * @dataProvider provideSecuritySystems + */ + public function testSwitchedUserExit(array $options) { - $client = $this->createAuthenticatedClient('user_can_switch'); + $client = $this->createAuthenticatedClient('user_can_switch', $options); $client->request('GET', '/profile?_switch_user=user_cannot_switch_1'); $client->request('GET', '/profile?_switch_user='.SwitchUserListener::EXIT_VALUE); @@ -51,9 +57,12 @@ public function testSwitchedUserExit() $this->assertEquals('user_can_switch', $client->getProfile()->getCollector('security')->getUser()); } - public function testSwitchUserStateless() + /** + * @dataProvider provideSecuritySystems + */ + public function testSwitchUserStateless(array $options) { - $client = $this->createClient(['test_case' => 'JsonLogin', 'root_config' => 'switchuser_stateless.yml']); + $client = $this->createClient(['test_case' => 'JsonLogin', 'root_config' => 'switchuser_stateless.yml'] + $options); $client->request('POST', '/chk', [], [], ['HTTP_X_SWITCH_USER' => 'dunglas', 'CONTENT_TYPE' => 'application/json'], '{"user": {"login": "user_can_switch", "password": "test"}}'); $response = $client->getResponse(); @@ -66,16 +75,20 @@ public function testSwitchUserStateless() public function getTestParameters() { return [ - 'unauthorized_user_cannot_switch' => ['user_cannot_switch_1', 'user_cannot_switch_1', 'user_cannot_switch_1', 403], - 'authorized_user_can_switch' => ['user_can_switch', 'user_cannot_switch_1', 'user_cannot_switch_1', 200], - 'authorized_user_cannot_switch_to_non_existent' => ['user_can_switch', 'user_does_not_exist', 'user_can_switch', 403], - 'authorized_user_can_switch_to_himself' => ['user_can_switch', 'user_can_switch', 'user_can_switch', 200], + 'unauthorized_user_cannot_switch' => ['user_cannot_switch_1', true, 'user_cannot_switch_1', 'user_cannot_switch_1', 403], + 'legacy_unauthorized_user_cannot_switch' => ['user_cannot_switch_1', false, 'user_cannot_switch_1', 'user_cannot_switch_1', 403], + 'authorized_user_can_switch' => ['user_can_switch', true, 'user_cannot_switch_1', 'user_cannot_switch_1', 200], + 'legacy_authorized_user_can_switch' => ['user_can_switch', false, 'user_cannot_switch_1', 'user_cannot_switch_1', 200], + 'authorized_user_cannot_switch_to_non_existent' => ['user_can_switch', true, 'user_does_not_exist', 'user_can_switch', 403], + 'legacy_authorized_user_cannot_switch_to_non_existent' => ['user_can_switch', false, 'user_does_not_exist', 'user_can_switch', 403], + 'authorized_user_can_switch_to_himself' => ['user_can_switch', true, 'user_can_switch', 'user_can_switch', 200], + 'legacy_authorized_user_can_switch_to_himself' => ['user_can_switch', false, 'user_can_switch', 'user_can_switch', 200], ]; } - protected function createAuthenticatedClient($username) + protected function createAuthenticatedClient($username, array $options = []) { - $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'switchuser.yml']); + $client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'switchuser.yml'] + $options); $client->followRedirects(true); $form = $client->request('GET', '/login')->selectButton('login')->form(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php index 8e622282c2c1d..72d23f03f30f7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php @@ -25,8 +25,9 @@ class AppKernel extends Kernel private $varDir; private $testCase; private $rootConfig; + private $authenticatorManagerEnabled; - public function __construct($varDir, $testCase, $rootConfig, $environment, $debug) + public function __construct($varDir, $testCase, $rootConfig, $environment, $debug, $authenticatorManagerEnabled = false) { if (!is_dir(__DIR__.'/'.$testCase)) { throw new \InvalidArgumentException(sprintf('The test case "%s" does not exist.', $testCase)); @@ -39,6 +40,7 @@ public function __construct($varDir, $testCase, $rootConfig, $environment, $debu throw new \InvalidArgumentException(sprintf('The root config "%s" does not exist.', $rootConfig)); } $this->rootConfig = $rootConfig; + $this->authenticatorManagerEnabled = $authenticatorManagerEnabled; parent::__construct($environment, $debug); } @@ -48,7 +50,7 @@ public function __construct($varDir, $testCase, $rootConfig, $environment, $debu */ public function getContainerClass(): string { - return parent::getContainerClass().substr(md5($this->rootConfig), -16); + return parent::getContainerClass().substr(md5($this->rootConfig.$this->authenticatorManagerEnabled), -16); } public function registerBundles(): iterable @@ -78,6 +80,14 @@ public function getLogDir(): string public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load($this->rootConfig); + + if ($this->authenticatorManagerEnabled) { + $loader->load(function ($container) { + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + ]); + }); + } } public function serialize() diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml index a0ed6f8e1e151..274ef33204130 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml @@ -19,7 +19,6 @@ security: remember_me: always_remember_me: true secret: key - anonymous: ~ access_control: - { path: ^/foo, roles: ROLE_USER } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/legacy_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/legacy_config.yml new file mode 100644 index 0000000000000..5dfc173869548 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/legacy_config.yml @@ -0,0 +1,7 @@ +imports: + - { resource: ./config.yml } + +security: + firewalls: + default: + anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml new file mode 100644 index 0000000000000..d6a80d5059471 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml @@ -0,0 +1,44 @@ +imports: + - { resource: ./../config/default.yml } + +services: + csrf_form_login.form.type: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType + arguments: + - '@request_stack' + tags: + - { name: form.type } + +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + providers: + in_memory: + memory: + users: + johannes: { password: test, roles: [ROLE_USER] } + + firewalls: + # This firewall doesn't make sense in combination with the rest of the + # configuration file, but it's here for testing purposes (do not use + # this file in a real world scenario though) + login_form: + pattern: ^/login$ + security: false + + default: + form_login: + check_path: /login_check + default_target_path: /profile + target_path_parameter: "user_login[_target_path]" + failure_path_parameter: "user_login[_failure_path]" + username_parameter: "user_login[username]" + password_parameter: "user_login[password]" + logout: + path: /logout_path + target: / + csrf_token_generator: security.csrf.token_manager + + access_control: + - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index 5a00ac329895d..98ba0eb5326ad 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -1,47 +1,9 @@ imports: - - { resource: ./../config/default.yml } - -services: - csrf_form_login.form.type: - class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType - arguments: - - '@request_stack' - tags: - - { name: form.type } + - { resource: ./base_config.yml } security: - encoders: - Symfony\Component\Security\Core\User\User: plaintext - - providers: - in_memory: - memory: - users: - johannes: { password: test, roles: [ROLE_USER] } - firewalls: - # This firewall doesn't make sense in combination with the rest of the - # configuration file, but it's here for testing purposes (do not use - # this file in a real world scenario though) - login_form: - pattern: ^/login$ - security: false - default: form_login: - check_path: /login_check - default_target_path: /profile - target_path_parameter: "user_login[_target_path]" - failure_path_parameter: "user_login[_failure_path]" - username_parameter: "user_login[username]" - password_parameter: "user_login[password]" + enable_csrf: true csrf_parameter: "user_login[_token]" - csrf_token_generator: security.csrf.token_manager - anonymous: ~ - logout: - path: /logout_path - target: / - csrf_token_generator: security.csrf.token_manager - - access_control: - - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/legacy_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/legacy_config.yml new file mode 100644 index 0000000000000..832579f64376f --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/legacy_config.yml @@ -0,0 +1,10 @@ +imports: + - { resource: ./base_config.yml } + +security: + firewalls: + default: + form_login: + csrf_token_generator: security.csrf.token_manager + csrf_parameter: "user_login[_token]" + anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/legacy_routes_as_path.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/legacy_routes_as_path.yml new file mode 100644 index 0000000000000..14ea6c0e5f1e8 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/legacy_routes_as_path.yml @@ -0,0 +1,13 @@ +imports: + - { resource: ./legacy_config.yml } + +security: + firewalls: + default: + form_login: + login_path: form_login + check_path: form_login_check + default_target_path: form_login_default_target_path + logout: + path: form_logout + target: form_login_homepage diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml index 43bb399bce6ab..302d7382762d8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml @@ -19,8 +19,6 @@ security: pattern: ^/secure/ http_basic: { realm: "Secure Gateway API" } entry_point: firewall_entry_point.entry_point.stub - default: - anonymous: ~ access_control: - { path: ^/secure/, roles: ROLE_SECURE } providers: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml index 3522f27f13898..055fcee19bd94 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml @@ -17,7 +17,6 @@ security: firewalls: main: pattern: ^/ - anonymous: true json_login: check_path: /chk username_path: user.login diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml index e15e203c626cc..c5076cce6fc27 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml @@ -14,7 +14,6 @@ security: firewalls: main: pattern: ^/ - anonymous: true json_login: check_path: /chk username_path: user.login diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/bundles.php similarity index 100% rename from src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php rename to src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/bundles.php diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_access.yml similarity index 96% rename from src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/config.yml rename to src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_access.yml index 2e20735b80236..f49d2f292b770 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_access.yml @@ -18,7 +18,6 @@ security: remember_me: true require_previous_session: false logout: ~ - anonymous: ~ stateless: true access_control: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_cookie_clearing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_cookie_clearing.yml new file mode 100644 index 0000000000000..f62cc616557a5 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_cookie_clearing.yml @@ -0,0 +1,27 @@ +imports: +- { resource: ./../config/framework.yml } + +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + providers: + in_memory: + memory: + users: + johannes: { password: test, roles: [ROLE_USER] } + + firewalls: + default: + form_login: + check_path: login + remember_me: true + require_previous_session: false + logout: + delete_cookies: + flavor: { path: null, domain: somedomain } + stateless: true + + access_control: + - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/routing.yml similarity index 100% rename from src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/routing.yml rename to src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/routing.yml diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml index 9e5563fea5197..9d92ac82c3c63 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml @@ -22,5 +22,4 @@ security: secret: secret logout: invalidate_session: false - anonymous: ~ stateless: true diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml index 78857765160d9..7f334ffcaee2f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml @@ -26,5 +26,4 @@ security: always_remember_me: true secret: key logout: ~ - anonymous: ~ stateless: true diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml index 7fc9f12174251..328242d279722 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml @@ -27,13 +27,11 @@ security: check_path: /login_check default_target_path: /profile logout: ~ - anonymous: ~ lazy: true # This firewall is here just to check its the logout functionality second_area: http_basic: ~ - anonymous: ~ logout: target: /second/target path: /second/logout diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml index cc6503affb265..c9fe56e56c739 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml @@ -15,7 +15,6 @@ security: default: form_login: ~ logout: ~ - anonymous: ~ access_control: # the '256.357.458.559' IP is wrong on purpose, to check invalid IP errors diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_config.yml new file mode 100644 index 0000000000000..41a607ca0335e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_config.yml @@ -0,0 +1,9 @@ +imports: + - { resource: ./config.yml } + +security: + firewalls: + default: + anonymous: ~ + second_area: + anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_localized_form_failure_handler.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_localized_form_failure_handler.yml new file mode 100644 index 0000000000000..4706234101067 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_localized_form_failure_handler.yml @@ -0,0 +1,7 @@ +imports: + - { resource: ./localized_form_failure_handler.yml } + +security: + firewalls: + default: + anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_localized_routes.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_localized_routes.yml new file mode 100644 index 0000000000000..df5da8cec9bf0 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_localized_routes.yml @@ -0,0 +1,7 @@ +imports: + - { resource: ./localized_routes.yml } + +security: + firewalls: + default: + anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_routes_as_path.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_routes_as_path.yml new file mode 100644 index 0000000000000..14ea6c0e5f1e8 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/legacy_routes_as_path.yml @@ -0,0 +1,13 @@ +imports: + - { resource: ./legacy_config.yml } + +security: + firewalls: + default: + form_login: + login_path: form_login + check_path: form_login_check + default_target_path: form_login_default_target_path + logout: + path: form_logout + target: form_login_homepage diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml index e01ed369b1f56..ced854a6819c9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml @@ -17,4 +17,3 @@ security: login_path: localized_login_path check_path: localized_check_path failure_handler: localized_form_failure_handler - anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml index 5251fd1d93de1..b07be914d45f2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml @@ -20,7 +20,6 @@ security: logout: path: localized_logout_path target: localized_logout_target_path - anonymous: ~ access_control: - { path: '^/(?:[a-z]{2})/secure/.*', roles: ROLE_USER } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index c1603160dd0cd..7b2575a51f103 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -26,7 +26,7 @@ "symfony/security-core": "^5.1", "symfony/security-csrf": "^4.4|^5.0", "symfony/security-guard": "^5.1", - "symfony/security-http": "^5.1" + "symfony/security-http": "^5.1,>=5.1.2" }, "require-dev": { "doctrine/doctrine-bundle": "^2.0", diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 7dad15620632b..97ccf68618b0b 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -106,7 +106,7 @@ private function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); - $inputStream = $this->inputStream ?: STDIN; + $inputStream = $this->inputStream ?: fopen('php://stdin', 'r'); $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { @@ -473,7 +473,7 @@ private function validateAttempts(callable $interviewer, OutputInterface $output } catch (\Exception $error) { } - $attempts = $attempts ?? -(int) $this->isTty(); + $attempts = $attempts ?? -(int) $this->askForever(); } throw $error; @@ -506,9 +506,13 @@ private function getShell() return self::$shell; } - private function isTty(): bool + private function askForever(): bool { - $inputStream = !$this->inputStream && \defined('STDIN') ? STDIN : $this->inputStream; + $inputStream = $this->inputStream ?: fopen('php://stdin', 'r'); + + if ('php://stdin' !== (stream_get_meta_data($inputStream)['url'] ?? null)) { + return true; + } if (\function_exists('stream_isatty')) { return stream_isatty($inputStream); diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 9afad24357d07..677d02776635c 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\Tests\Helper; +use Symfony\Component\Console\Application; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\FormatterHelper; @@ -21,6 +22,7 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Terminal; +use Symfony\Component\Console\Tester\ApplicationTester; /** * @group tty @@ -727,21 +729,36 @@ public function testAskThrowsExceptionOnMissingInputWithValidator() $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), $question); } - public function testAskThrowsExceptionFromValidatorEarlyWhenTtyIsMissing() + public function testQuestionValidatorRepeatsThePrompt() { - $this->expectException('Exception'); - $this->expectExceptionMessage('Bar, not Foo'); + $tries = 0; + $application = new Application(); + $application->setAutoExit(false); + $application->register('question') + ->setCode(function ($input, $output) use (&$tries) { + $question = new Question('This is a promptable question'); + $question->setValidator(function ($value) use (&$tries) { + ++$tries; + if (!$value) { + throw new \Exception(); + } - $output = $this->getMockBuilder('\Symfony\Component\Console\Output\OutputInterface')->getMock(); - $output->expects($this->once())->method('writeln'); + return $value; + }); + + (new QuestionHelper())->ask($input, $output, $question); - (new QuestionHelper())->ask( - $this->createStreamableInputInterfaceMock($this->getInputStream('Foo'), true), - $output, - (new Question('Q?'))->setHidden(true)->setValidator(function ($input) { - throw new \Exception("Bar, not $input"); + return 0; }) - ); + ; + + $tester = new ApplicationTester($application); + $tester->setInputs(['', 'not-empty']); + + $statusCode = $tester->run(['command' => 'question'], ['interactive' => true]); + + $this->assertSame(2, $tries); + $this->assertSame($statusCode, 0); } public function testEmptyChoices() diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php index d4023ecb5f389..758ef08bb9ee5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php @@ -72,7 +72,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) */ public function finishView(FormView $view, FormInterface $form, array $options) { - $prefixOffset = -1; + $prefixOffset = -2; // check if the entry type also defines a block prefix /** @var FormInterface $entry */ foreach ($form as $entry) { @@ -93,7 +93,7 @@ public function finishView(FormView $view, FormInterface $form, array $options) $view->vars['multipart'] = true; } - if ($prefixOffset > -2 && $prototype->getConfig()->getOption('block_prefix')) { + if ($prefixOffset > -3 && $prototype->getConfig()->getOption('block_prefix')) { --$prefixOffset; } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index a610c24a044b3..706c3f8506291 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -415,8 +415,8 @@ public function testEntriesBlockPrefixes() $expectedBlockPrefixes = [ 'form', - 'text', 'collection_entry', + 'text', '_fields_entry', ]; @@ -428,37 +428,66 @@ public function testEntriesBlockPrefixes() public function testEntriesBlockPrefixesWithCustomBlockPrefix() { $collectionView = $this->factory->createNamed('fields', static::TESTED_TYPE, [''], [ + 'allow_add' => true, 'entry_options' => ['block_prefix' => 'field'], ]) ->createView() ; - $this->assertCount(1, $collectionView); - $this->assertSame([ + $expectedBlockPrefixes = [ 'form', - 'text', 'collection_entry', + 'text', 'field', '_fields_entry', - ], $collectionView[0]->vars['block_prefixes']); + ]; + + $this->assertCount(1, $collectionView); + $this->assertSame($expectedBlockPrefixes, $collectionView[0]->vars['block_prefixes']); + $this->assertSame($expectedBlockPrefixes, $collectionView->vars['prototype']->vars['block_prefixes']); } public function testEntriesBlockPrefixesWithCustomBlockPrefixedType() { $collectionView = $this->factory->createNamed('fields', static::TESTED_TYPE, [''], [ + 'allow_add' => true, 'entry_type' => BlockPrefixedFooTextType::class, ]) ->createView() ; - $this->assertCount(1, $collectionView); - $this->assertSame([ + $expectedBlockPrefixes = [ 'form', - 'block_prefixed_foo_text', 'collection_entry', + 'block_prefixed_foo_text', 'foo', '_fields_entry', - ], $collectionView[0]->vars['block_prefixes']); + ]; + + $this->assertCount(1, $collectionView); + $this->assertSame($expectedBlockPrefixes, $collectionView[0]->vars['block_prefixes']); + $this->assertSame($expectedBlockPrefixes, $collectionView->vars['prototype']->vars['block_prefixes']); + } + + public function testPrototypeBlockPrefixesWithCustomBlockPrefix() + { + $collectionView = $this->factory->createNamed('fields', static::TESTED_TYPE, [], [ + 'allow_add' => true, + 'entry_options' => ['block_prefix' => 'field'], + ]) + ->createView() + ; + + $expectedBlockPrefixes = [ + 'form', + 'collection_entry', + 'text', + 'field', + '_fields_entry', + ]; + + $this->assertCount(0, $collectionView); + $this->assertSame($expectedBlockPrefixes, $collectionView->vars['prototype']->vars['block_prefixes']); } public function testSubmitNull($expected = null, $norm = null, $view = null) diff --git a/src/Symfony/Component/HttpFoundation/InputBag.php b/src/Symfony/Component/HttpFoundation/InputBag.php index 97fbe9032243b..b2aff595c4929 100644 --- a/src/Symfony/Component/HttpFoundation/InputBag.php +++ b/src/Symfony/Component/HttpFoundation/InputBag.php @@ -36,29 +36,18 @@ public function get(string $key, $default = null) $value = parent::get($key, $this); if (null !== $value && $this !== $value && !is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { - trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-string value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all()" instead.', __METHOD__, BadRequestException::class, __CLASS__); + trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-string value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__); } return $this === $value ? $default : $value; } /** - * Returns the inputs. - * - * @param string|null $key The name of the input to return or null to get them all + * {@inheritdoc} */ public function all(string $key = null): array { - if (null === $key) { - return $this->parameters; - } - - $value = $this->parameters[$key] ?? []; - if (!\is_array($value)) { - throw new BadRequestException(sprintf('Unexpected value for "%s" input, expecting "array", got "%s".', $key, get_debug_type($value))); - } - - return $value; + return parent::all($key); } /** diff --git a/src/Symfony/Component/HttpFoundation/ParameterBag.php b/src/Symfony/Component/HttpFoundation/ParameterBag.php index 212149c7b99f0..dc45bec4d505d 100644 --- a/src/Symfony/Component/HttpFoundation/ParameterBag.php +++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + /** * ParameterBag is a container for key/value pairs. * @@ -31,11 +33,23 @@ public function __construct(array $parameters = []) /** * Returns the parameters. * + * @param string|null $key The name of the parameter to return or null to get them all + * * @return array An array of parameters */ - public function all() + public function all(/*string $key = null*/) { - return $this->parameters; + $key = \func_num_args() > 0 ? func_get_arg(0) : null; + + if (null === $key) { + return $this->parameters; + } + + if (!\is_array($value = $this->parameters[$key] ?? [])) { + throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value))); + } + + return $value; } /** diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 098ce6d251e04..e737a7e58c832 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -85,7 +85,7 @@ class Request /** * Request body parameters ($_POST). * - * @var InputBag + * @var InputBag|ParameterBag */ public $request; @@ -268,7 +268,7 @@ public function __construct(array $query = [], array $request = [], array $attri */ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { - $this->request = new InputBag($request); + $this->request = new ParameterBag($request); $this->query = new InputBag($query); $this->attributes = new ParameterBag($attributes); $this->cookies = new InputBag($cookies); @@ -298,7 +298,9 @@ public static function createFromGlobals() { $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); - if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') + if ($_POST) { + $request->request = new InputBag($_POST); + } elseif (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) ) { parse_str($request->getContent(), $data); @@ -447,7 +449,7 @@ public function duplicate(array $query = null, array $request = null, array $att $dup->query = new InputBag($query); } if (null !== $request) { - $dup->request = new InputBag($request); + $dup->request = new ParameterBag($request); } if (null !== $attributes) { $dup->attributes = new ParameterBag($attributes); diff --git a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php index febe5eda62f01..bd6f3114d7eb9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\InputBag; class InputBagTest extends TestCase @@ -36,21 +35,6 @@ public function testGetDoesNotUseDeepByDefault() $this->assertNull($bag->get('foo[bar]')); } - public function testAllWithInputKey() - { - $bag = new InputBag(['foo' => ['bar', 'baz'], 'null' => null]); - - $this->assertEquals(['bar', 'baz'], $bag->all('foo'), '->all() gets the value of a parameter'); - $this->assertEquals([], $bag->all('unknown'), '->all() returns an empty array if a parameter is not defined'); - } - - public function testAllThrowsForNonArrayValues() - { - $this->expectException(BadRequestException::class); - $bag = new InputBag(['foo' => 'bar', 'null' => null]); - $bag->all('foo'); - } - public function testFilterArray() { $bag = new InputBag([ @@ -77,7 +61,7 @@ public function testSetWithNonStringishOrArrayIsDeprecated() public function testGettingANonStringValueIsDeprecated() { $bag = new InputBag(['foo' => ['a', 'b']]); - $this->expectDeprecation('Since symfony/http-foundation 5.1: Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated, and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" exception in Symfony 6.0, use "Symfony\Component\HttpFoundation\InputBag::all()" instead.'); + $this->expectDeprecation('Since symfony/http-foundation 5.1: Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated, and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" exception in Symfony 6.0, use "Symfony\Component\HttpFoundation\InputBag::all($key)" instead.'); $bag->get('foo'); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index d2a5c991cc7f6..d7cc3ca249eb7 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\ParameterBag; class ParameterBagTest extends TestCase @@ -27,6 +28,21 @@ public function testAll() $this->assertEquals(['foo' => 'bar'], $bag->all(), '->all() gets all the input'); } + public function testAllWithInputKey() + { + $bag = new ParameterBag(['foo' => ['bar', 'baz'], 'null' => null]); + + $this->assertEquals(['bar', 'baz'], $bag->all('foo'), '->all() gets the value of a parameter'); + $this->assertEquals([], $bag->all('unknown'), '->all() returns an empty array if a parameter is not defined'); + } + + public function testAllThrowsForNonArrayValues() + { + $this->expectException(BadRequestException::class); + $bag = new ParameterBag(['foo' => 'bar', 'null' => null]); + $bag->all('foo'); + } + public function testKeys() { $bag = new ParameterBag(['foo' => 'bar']); diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index b8c57fc92418e..99cd54884f51c 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\InputBag; +use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; @@ -1255,6 +1257,11 @@ public function testCreateFromGlobals($method) { $normalizedMethod = strtoupper($method); + $_POST = []; + $request = Request::createFromGlobals(); + $this->assertNotInstanceOf(InputBag::class, $request->request); + $this->assertInstanceOf(ParameterBag::class, $request->request); + $_GET['foo1'] = 'bar1'; $_POST['foo2'] = 'bar2'; $_COOKIE['foo3'] = 'bar3'; @@ -1267,6 +1274,8 @@ public function testCreateFromGlobals($method) $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE'); $this->assertEquals(['bar4'], $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES'); $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER'); + $this->assertInstanceOf(InputBag::class, $request->request); + $this->assertInstanceOf(ParameterBag::class, $request->request); unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']); @@ -1275,6 +1284,8 @@ public function testCreateFromGlobals($method) $request = RequestContentProxy::createFromGlobals(); $this->assertEquals($normalizedMethod, $request->getMethod()); $this->assertEquals('mycontent', $request->request->get('content')); + $this->assertInstanceOf(InputBag::class, $request->request); + $this->assertInstanceOf(ParameterBag::class, $request->request); unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']); diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index ceb069ee2c4fb..178585762db2a 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,11 +73,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.1'; - const VERSION_ID = 50101; + const VERSION = '5.1.2'; + const VERSION_ID = 50102; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; - const RELEASE_VERSION = 1; + const RELEASE_VERSION = 2; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2021'; diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php index 97a106a616a6d..9a64428c98b89 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php @@ -12,6 +12,7 @@ namespace Symfony\Component\PropertyInfo\Extractor; use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Types\Context; @@ -88,10 +89,12 @@ public function getShortDescription(string $class, string $property, array $cont } foreach ($docBlock->getTagsByName('var') as $var) { - $varDescription = $var->getDescription()->render(); + if ($var && !$var instanceof InvalidTag) { + $varDescription = $var->getDescription()->render(); - if (!empty($varDescription)) { - return $varDescription; + if (!empty($varDescription)) { + return $varDescription; + } } } @@ -142,7 +145,7 @@ public function getTypes(string $class, string $property, array $context = []): $types = []; /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ foreach ($docBlock->getTagsByName($tag) as $tag) { - if ($tag && null !== $tag->getType()) { + if ($tag && !$tag instanceof InvalidTag && null !== $tag->getType()) { $types = array_merge($types, $this->phpDocTypeHelper->getTypes($tag->getType())); } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 0d3c32206786e..d352fa12b61f0 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\PropertyInfo\Tests\Extractor; +use phpDocumentor\Reflection\DocBlock\StandardTagFactory; +use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; use phpDocumentor\Reflection\Types\Collection; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; @@ -46,6 +48,26 @@ public function testParamTagTypeIsOmitted() $this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType')); } + public function invalidTypesProvider() + { + return [ + 'pub' => ['pub', null, null], + 'stat' => ['stat', null, null], + 'foo' => ['foo', $this->isPhpDocumentorV5() ? 'Foo.' : null, null], + 'bar' => ['bar', $this->isPhpDocumentorV5() ? 'Bar.' : null, null], + ]; + } + + /** + * @dataProvider invalidTypesProvider + */ + public function testInvalid($property, $shortDescription, $longDescription) + { + $this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property)); + $this->assertSame($shortDescription, $this->extractor->getShortDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property)); + $this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property)); + } + /** * @dataProvider typesWithNoPrefixesProvider */ @@ -94,7 +116,7 @@ public function typesProvider() ['donotexist', null, null, null], ['staticGetter', null, null, null], ['staticSetter', null, null, null], - ['emptyVar', null, null, null], + ['emptyVar', null, $this->isPhpDocumentorV5() ? 'This should not be removed.' : null, null], ]; } @@ -250,6 +272,16 @@ public function testDocBlockFallback($property, $types) { $this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback', $property)); } + + protected function isPhpDocumentorV5() + { + if (class_exists(InvalidTag::class)) { + return true; + } + + return (new \ReflectionMethod(StandardTagFactory::class, 'create')) + ->hasReturnType(); + } } class EmptyDocBlock diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/InvalidDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/InvalidDummy.php new file mode 100644 index 0000000000000..0a4cc81f36be8 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/InvalidDummy.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + +/** + * @author Martin Rademacher + */ +class InvalidDummy +{ + /** + * @var + */ + public $pub; + + /** + * @return + */ + public static function getStat() + { + return 'stat'; + } + + /** + * Foo. + * + * @param + */ + public function setFoo($foo) + { + } + + /** + * Bar. + * + * @return + */ + public function getBar() + { + return 'bar'; + } +} diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index 1c8eef462e817..4c07da473ded6 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -31,7 +31,7 @@ "symfony/serializer": "^4.4|^5.0", "symfony/cache": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "doctrine/annotations": "~1.7" }, "conflict": { diff --git a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php index d165fbceb191a..b277082a846d1 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php @@ -79,7 +79,14 @@ public function supports(Request $request): ?bool public function authenticate(Request $request): PassportInterface { - $credentials = $this->getCredentials($request); + try { + $credentials = $this->getCredentials($request); + } catch (BadRequestHttpException $e) { + $request->setRequestFormat('json'); + + throw $e; + } + $user = $this->userProvider->loadUserByUsername($credentials['username']); if (!$user instanceof UserInterface) { throw new AuthenticationServiceException('The user provider must return a UserInterface object.');