Skip to content
Permalink
Browse files

Merge branch '4.4' into 5.0

* 4.4:
  [DI] auto-register singly implemented interfaces by default
  [DI] fix overriding existing services with aliases for singly-implemented interfaces
  remove service when base class is missing
  do not depend on the QueryBuilder from the ORM
  [Security/Http] call auth listeners/guards eagerly when they "support" the request
  [Messenger] add tests to FailedMessagesShowCommand
  Fix the translation commands when a template contains a syntax error
  [Security] Fix clearing remember-me cookie after deauthentication
  [Validator] Update Slovenian translations
  [HttpClient] remove conflict rule with HttpKernel that prevents using the component in Symfony 3.4
  [Config][ReflectionClassResource] Handle parameters with undefined constant as their default values
  fix dumping number-like string parameters
  Fix CI
  [Console] Fix autocomplete multibyte input support
  [Config] don't break on virtual stack frames in ClassExistenceResource
  more robust initialization from request
  Changing the multipart form-data behavior to use the form name as an array, which makes it recognizable as an array by PHP on the $_POST globals once it is coming from the HttpClient component
  • Loading branch information
nicolas-grekas committed Nov 30, 2019
2 parents 5a30793 + 9b658ed commit bb11cac33e27d8b3f54aa7c50d6a8c6148271f6e
Showing with 970 additions and 297 deletions.
  1. +5 −4 .travis.yml
  2. +1 −0 UPGRADE-4.4.md
  3. +1 −1 UPGRADE-5.0.md
  4. +4 −2 src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
  5. +11 −1 src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php
  6. +7 −16 src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php
  7. +1 −8 src/Symfony/Bridge/Twig/Translation/TwigExtractor.php
  8. +5 −1 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php
  9. +9 −6 src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
  10. +0 −2 src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
  11. +29 −20 src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php
  12. +59 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php
  13. +77 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php
  14. +24 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php
  15. +18 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/bundles.php
  16. +31 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml
  17. +7 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/routing.yml
  18. +15 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php
  19. +22 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml
  20. +5 −0 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml
  21. +1 −1 src/Symfony/Bundle/SecurityBundle/composer.json
  22. +1 −0 src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php
  23. +7 −5 src/Symfony/Component/Config/Resource/ClassExistenceResource.php
  24. +47 −3 src/Symfony/Component/Config/Resource/ReflectionClassResource.php
  25. +15 −3 src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php
  26. +3 −3 src/Symfony/Component/Console/Helper/QuestionHelper.php
  27. +6 −4 src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
  28. +5 −0 src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
  29. +6 −1 src/Symfony/Component/DependencyInjection/Loader/FileLoader.php
  30. +2 −0 src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php
  31. +2 −0 src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
  32. +2 −0 src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
  33. +11 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container8.php
  34. +11 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php
  35. +11 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml
  36. +11 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services8.yml
  37. +3 −6 src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php
  38. +0 −3 src/Symfony/Component/HttpClient/composer.json
  39. +98 −0 src/Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php
  40. +13 −4 src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php
  41. +28 −0 src/Symfony/Component/Mime/Tests/Part/Multipart/FormDataPartTest.php
  42. +2 −2 src/Symfony/Component/Routing/RequestContext.php
  43. +1 −0 src/Symfony/Component/Security/CHANGELOG.md
  44. +36 −16 src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php
  45. +1 −1 src/Symfony/Component/Security/Guard/composer.json
  46. +10 −6 src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php
  47. +42 −0 src/Symfony/Component/Security/Http/Firewall/AbstractListener.php
  48. +19 −8 src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php
  49. +18 −4 src/Symfony/Component/Security/Http/Firewall/AccessListener.php
  50. +11 −2 src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php
  51. +10 −2 src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php
  52. +16 −12 src/Symfony/Component/Security/Http/Firewall/ChannelListener.php
  53. +21 −2 src/Symfony/Component/Security/Http/Firewall/ContextListener.php
  54. +10 −6 src/Symfony/Component/Security/Http/Firewall/LogoutListener.php
  55. +11 −2 src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php
  56. +21 −8 src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php
  57. +13 −5 src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php
  58. +11 −43 src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php
  59. +5 −3 src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php
  60. +20 −2 src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
  61. +30 −79 src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php
  62. +48 −0 src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf
@@ -266,6 +266,11 @@ install:
run_tests () {
set -e
export PHP=$1
if [[ !$deps && $PHP = 7.2 ]]; then
tfold src/Symfony/Component/HttpClient.h2push "$COMPOSER_UP symfony/contracts && docker run -it --rm -v $(pwd):/app -v $(phpenv which composer):/usr/local/bin/composer -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push"
fi
if [[ $PHP != 7.4* && $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then
echo -e "\\n\\e[33;1mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m"
return
@@ -312,10 +317,6 @@ install:
PHPUNIT_X="$PHPUNIT_X,legacy"
fi
if [[ $PHP = ${MIN_PHP%.*} ]]; then
tfold src/Symfony/Component/HttpClient.h2push docker run -it --rm -v $(pwd):/app -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push
fi
echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}"
tfold src/Symfony/Component/Console.tty $PHPUNIT --group tty
@@ -222,6 +222,7 @@ Security
* The `LdapUserProvider` class has been deprecated, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead.
* Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` should add a new `needsRehash()` method
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. Please explicitly return `false` to indicate invalid credentials.
* The `ListenerInterface` is deprecated, extend `AbstractListener` instead.
* Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` (and indirectly the `is_granted()` Twig and ExpressionLanguage function)

**Before**
@@ -437,7 +437,7 @@ Security
* `SimpleAuthenticatorInterface`, `SimpleFormAuthenticatorInterface`, `SimplePreAuthenticatorInterface`,
`SimpleAuthenticationProvider`, `SimpleAuthenticationHandler`, `SimpleFormAuthenticationListener` and
`SimplePreAuthenticationListener` have been removed. Use Guard instead.
* The `ListenerInterface` has been removed, turn your listeners into callables instead.
* The `ListenerInterface` has been removed, extend `AbstractListener` instead.
* The `Firewall::handleRequest()` method has been removed, use `Firewall::callListeners()` instead.
* `\Serializable` interface has been removed from `AbstractToken` and `AuthenticationException`,
thus `serialize()` and `unserialize()` aren't available.
@@ -14,7 +14,6 @@
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader;
@@ -82,13 +81,16 @@ public static function createChoiceName(object $choice, $key, string $value): st
* For instance in ORM two query builders with an equal SQL string and
* equal parameters are considered to be equal.
*
* @param object $queryBuilder A query builder, type declaration is not present here as there
* is no common base class for the different implementations
*
* @return array|null Array with important QueryBuilder parts or null if
* they can't be determined
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public function getQueryBuilderPartsForCachingHash(QueryBuilder $queryBuilder): ?array
public function getQueryBuilderPartsForCachingHash($queryBuilder): ?array
{
return null;
}
@@ -50,6 +50,10 @@ public function configureOptions(OptionsResolver $resolver)
*/
public function getLoader(ObjectManager $manager, QueryBuilder $queryBuilder, string $class)
{
if (!$queryBuilder instanceof QueryBuilder) {
throw new \TypeError(sprintf('Expected an instance of %s, but got %s.', QueryBuilder::class, \is_object($queryBuilder) ? \get_class($queryBuilder) : \gettype($queryBuilder)));
}
return new ORMQueryBuilderLoader($queryBuilder);
}
@@ -65,11 +69,17 @@ public function getBlockPrefix()
* We consider two query builders with an equal SQL string and
* equal parameters to be equal.
*
* @param QueryBuilder $queryBuilder
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public function getQueryBuilderPartsForCachingHash(QueryBuilder $queryBuilder): ?array
public function getQueryBuilderPartsForCachingHash($queryBuilder): ?array
{
if (!$queryBuilder instanceof QueryBuilder) {
throw new \TypeError(sprintf('Expected an instance of %s, but got %s.', QueryBuilder::class, \is_object($queryBuilder) ? \get_class($queryBuilder) : \gettype($queryBuilder)));
}
return [
$queryBuilder->getQuery()->getSQL(),
array_map([$this, 'parameterToArray'], $queryBuilder->getParameters()->toArray()),
@@ -17,7 +17,6 @@
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
use Twig\Error\Error;
use Twig\Loader\ArrayLoader;
class TwigExtractorTest extends TestCase
@@ -74,31 +73,23 @@ public function getExtractData()
/**
* @dataProvider resourcesWithSyntaxErrorsProvider
*/
public function testExtractSyntaxError($resources)
public function testExtractSyntaxError($resources, array $messages)
{
$this->expectException('Twig\Error\Error');
$twig = new Environment($this->getMockBuilder('Twig\Loader\LoaderInterface')->getMock());
$twig->addExtension(new TranslationExtension($this->getMockBuilder(TranslatorInterface::class)->getMock()));
$extractor = new TwigExtractor($twig);
try {
$extractor->extract($resources, new MessageCatalogue('en'));
} catch (Error $e) {
$this->assertSame(\dirname(__DIR__).strtr('/Fixtures/extractor/syntax_error.twig', '/', \DIRECTORY_SEPARATOR), $e->getFile());
$this->assertSame(1, $e->getLine());
$this->assertSame('Unclosed "block".', $e->getMessage());
throw $e;
}
$catalogue = new MessageCatalogue('en');
$extractor->extract($resources, $catalogue);
$this->assertSame($messages, $catalogue->all());
}
public function resourcesWithSyntaxErrorsProvider(): array
{
return [
[__DIR__.'/../Fixtures'],
[__DIR__.'/../Fixtures/extractor/syntax_error.twig'],
[new \SplFileInfo(__DIR__.'/../Fixtures/extractor/syntax_error.twig')],
[__DIR__.'/../Fixtures', ['messages' => ['Hi!' => 'Hi!']]],
[__DIR__.'/../Fixtures/extractor/syntax_error.twig', []],
[new \SplFileInfo(__DIR__.'/../Fixtures/extractor/syntax_error.twig'), []],
];
}
@@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\Translation;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Translation\Extractor\AbstractFileExtractor;
use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\MessageCatalogue;
@@ -58,13 +57,7 @@ public function extract($resource, MessageCatalogue $catalogue)
try {
$this->extractTemplate(file_get_contents($file->getPathname()), $catalogue);
} catch (Error $e) {
if ($file instanceof \SplFileInfo) {
$path = $file->getRealPath() ?: $file->getPathname();
$name = $file instanceof SplFileInfo ? $file->getRelativePathname() : $path;
$e->setSourceContext(new Source('', $name, $path));
}
throw $e;
// ignore errors, these should be fixed by using the linter
}
}
}
@@ -83,7 +83,11 @@ public function create(ContainerBuilder $container, string $id, array $config, ?
throw new \RuntimeException('Each "security.remember_me_aware" tag must have a provider attribute.');
}
$userProviders[] = new Reference($attribute['provider']);
// context listeners don't need a provider
if ('none' !== $attribute['provider']) {
$userProviders[] = new Reference($attribute['provider']);
}
$container
->getDefinition($serviceId)
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])
@@ -315,10 +315,11 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
$listeners[] = new Reference('security.channel_listener');
$contextKey = null;
$contextListenerId = null;
// Context serializer listener
if (false === $firewall['stateless']) {
$contextKey = $firewall['context'] ?? $id;
$listeners[] = new Reference($this->createContextListener($container, $contextKey));
$listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey));
$sessionStrategyId = 'security.authentication.session_strategy';
} else {
$this->statelessFirewallKeys[] = $id;
@@ -391,7 +392,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
$configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
// Authentication listeners
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint);
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId);
$config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint);
@@ -404,9 +405,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
}
// Access listener
if ($firewall['stateless'] || empty($firewall['anonymous']['lazy'])) {
$listeners[] = new Reference('security.access_listener');
}
$listeners[] = new Reference('security.access_listener');
// Exception listener
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
@@ -444,7 +443,7 @@ private function createContextListener(ContainerBuilder $container, string $cont
return $this->contextListeners[$contextKey] = $listenerId;
}
private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint)
private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint, string $contextListenerId = null)
{
$listeners = [];
$hasListeners = false;
@@ -462,6 +461,10 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
} elseif ('remember_me' === $key || 'anonymous' === $key) {
// RememberMeFactory will use the firewall secret when created, AnonymousAuthenticationListener does not load users.
$userProvider = null;
if ('remember_me' === $key && $contextListenerId) {
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
}
} elseif ($defaultProvider) {
$userProvider = $defaultProvider;
} elseif (empty($providerIds)) {
@@ -151,9 +151,7 @@
<argument type="service" id="security.exception_listener" />
<argument /> <!-- LogoutListener -->
<argument /> <!-- FirewallConfig -->
<argument type="service" id="security.access_listener" />
<argument type="service" id="security.untracked_token_storage" />
<argument type="service" id="security.access_map" />
</service>

<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">
@@ -13,11 +13,8 @@
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
@@ -28,17 +25,13 @@
*/
class LazyFirewallContext extends FirewallContext
{
private $accessListener;
private $tokenStorage;
private $map;
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, AccessListener $accessListener, TokenStorage $tokenStorage, AccessMapInterface $map)
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage)
{
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
$this->accessListener = $accessListener;
$this->tokenStorage = $tokenStorage;
$this->map = $map;
}
public function getListeners(): iterable
@@ -48,21 +41,37 @@ public function getListeners(): iterable
public function __invoke(RequestEvent $event)
{
$this->tokenStorage->setInitializer(function () use ($event) {
$event = new LazyResponseEvent($event);
foreach (parent::getListeners() as $listener) {
$listener($event);
$listeners = [];
$request = $event->getRequest();
$lazy = $request->isMethodCacheable();
foreach (parent::getListeners() as $listener) {
if (!$lazy || !$listener instanceof AbstractListener) {
$listeners[] = $listener;
$lazy = $lazy && $listener instanceof AbstractListener;
} elseif (false !== $supports = $listener->supports($request)) {
$listeners[] = [$listener, 'authenticate'];
$lazy = null === $supports;
}
});
}
try {
[$attributes] = $this->map->getPatterns($event->getRequest());
if (!$lazy) {
foreach ($listeners as $listener) {
$listener($event);
if ($attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes) {
($this->accessListener)($event);
if ($event->hasResponse()) {
return;
}
}
} catch (LazyResponseException $e) {
$event->setResponse($e->getResponse());
return;
}
$this->tokenStorage->setInitializer(function () use ($event, $listeners) {
$event = new LazyResponseEvent($event);
foreach ($listeners as $listener) {
$listener($event);
}
});
}
}
@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class AppCustomAuthenticator extends AbstractGuardAuthenticator
{
public function supports(Request $request)
{
return true;
}
public function getCredentials(Request $request)
{
throw new AuthenticationException('This should be hit');
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
}
public function checkCredentials($credentials, UserInterface $user)
{
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new Response('', 418);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
}
public function start(Request $request, AuthenticationException $authException = null)
{
return new Response($authException->getMessage(), Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
}
}

0 comments on commit bb11cac

Please sign in to comment.
You can’t perform that action at this time.