From ca630e5351fb014de61f3f6fee16f844688ab19a Mon Sep 17 00:00:00 2001 From: Jorge Vahldick Date: Fri, 18 Oct 2019 15:20:21 +0100 Subject: [PATCH 01/17] 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 --- .../Mime/Part/Multipart/FormDataPart.php | 17 ++++++++--- .../Tests/Part/Multipart/FormDataPartTest.php | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php b/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php index 88aa1a316a..6838620325 100644 --- a/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php +++ b/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php @@ -56,11 +56,20 @@ final class FormDataPart extends AbstractMultipartPart private function prepareFields(array $fields): array { $values = []; - array_walk_recursive($fields, function ($item, $key) use (&$values) { - if (!\is_array($item)) { - $values[] = $this->preparePart($key, $item); + + $prepare = function ($item, $key, $root = null) use (&$values, &$prepare) { + $fieldName = $root ? sprintf('%s[%s]', $root, $key) : $key; + + if (\is_array($item)) { + array_walk($item, $prepare, $fieldName); + + return; } - }); + + $values[] = $this->preparePart($fieldName, $item); + }; + + array_walk($fields, $prepare); return $values; } diff --git a/src/Symfony/Component/Mime/Tests/Part/Multipart/FormDataPartTest.php b/src/Symfony/Component/Mime/Tests/Part/Multipart/FormDataPartTest.php index 71a03e6863..a74ecea316 100644 --- a/src/Symfony/Component/Mime/Tests/Part/Multipart/FormDataPartTest.php +++ b/src/Symfony/Component/Mime/Tests/Part/Multipart/FormDataPartTest.php @@ -47,6 +47,34 @@ class FormDataPartTest extends TestCase $this->assertEquals([$t, $b, $c], $f->getParts()); } + public function testNestedArrayParts() + { + $p1 = new TextPart('content', 'utf-8', 'plain', '8bit'); + $f = new FormDataPart([ + 'foo' => clone $p1, + 'bar' => [ + 'baz' => [ + clone $p1, + 'qux' => clone $p1, + ], + ], + ]); + + $this->assertEquals('multipart', $f->getMediaType()); + $this->assertEquals('form-data', $f->getMediaSubtype()); + + $p1->setName('foo'); + $p1->setDisposition('form-data'); + + $p2 = clone $p1; + $p2->setName('bar[baz][0]'); + + $p3 = clone $p1; + $p3->setName('bar[baz][qux]'); + + $this->assertEquals([$p1, $p2, $p3], $f->getParts()); + } + public function testToString() { $p = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif'); From c6ed0f020880595dee3282c9088ba2f0f065bd74 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 27 Nov 2019 10:48:32 +0100 Subject: [PATCH 02/17] more robust initialization from request Request::getPort is declared as int|string but can actually return null. --- src/Symfony/Component/Routing/RequestContext.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Routing/RequestContext.php b/src/Symfony/Component/Routing/RequestContext.php index 8ebad8e253..ed50cd70d8 100644 --- a/src/Symfony/Component/Routing/RequestContext.php +++ b/src/Symfony/Component/Routing/RequestContext.php @@ -67,8 +67,8 @@ class RequestContext $this->setMethod($request->getMethod()); $this->setHost($request->getHost()); $this->setScheme($request->getScheme()); - $this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort()); - $this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort); + $this->setHttpPort($request->isSecure() || null === $request->getPort() ? $this->httpPort : $request->getPort()); + $this->setHttpsPort($request->isSecure() && null !== $request->getPort() ? $request->getPort() : $this->httpsPort); $this->setQueryString($request->server->get('QUERY_STRING', '')); return $this; From fffeccd7445092bb7ec140adfb827fb677096aad Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 28 Nov 2019 16:39:39 +0100 Subject: [PATCH 03/17] [Config] don't break on virtual stack frames in ClassExistenceResource --- .../Config/Resource/ClassExistenceResource.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index 1ba8e76248..340e28f245 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -191,15 +191,17 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ } $props = [ - 'file' => $trace[$i]['file'], - 'line' => $trace[$i]['line'], + 'file' => isset($trace[$i]['file']) ? $trace[$i]['file'] : null, + 'line' => isset($trace[$i]['line']) ? $trace[$i]['line'] : null, 'trace' => \array_slice($trace, 1 + $i), ]; foreach ($props as $p => $v) { - $r = new \ReflectionProperty('Exception', $p); - $r->setAccessible(true); - $r->setValue($e, $v); + if (null !== $v) { + $r = new \ReflectionProperty('Exception', $p); + $r->setAccessible(true); + $r->setValue($e, $v); + } } } From a1129f938c63c2b91bf3da20cd41a662c241433c Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Sat, 23 Nov 2019 18:59:57 +0100 Subject: [PATCH 04/17] [Console] Fix autocomplete multibyte input support --- .../Console/Helper/QuestionHelper.php | 6 +++--- .../Tests/Helper/QuestionHelperTest.php | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index af4d0b9cca..cccaeb0248 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -264,7 +264,7 @@ class QuestionHelper extends Helper } elseif ("\177" === $c) { // Backspace Character if (0 === $numMatches && 0 !== $i) { --$i; - $fullChoice = substr($fullChoice, 0, -1); + $fullChoice = self::substr($fullChoice, 0, -1); // Move cursor backwards $output->write("\033[1D"); } @@ -278,7 +278,7 @@ class QuestionHelper extends Helper } // Pop the last character off the end of our string - $ret = substr($ret, 0, $i); + $ret = self::substr($ret, 0, $i); } elseif ("\033" === $c) { // Did we read an escape sequence? $c .= fread($inputStream, 2); @@ -304,7 +304,7 @@ class QuestionHelper extends Helper $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); $output->write($remainingCharacters); $fullChoice .= $remainingCharacters; - $i = \strlen($fullChoice); + $i = self::strlen($fullChoice); } if ("\n" === $c) { diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 02cc6ce7e0..2e91d49fee 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -175,19 +175,20 @@ class QuestionHelperTest extends AbstractQuestionHelperTest // Acm // AcsTest // - // - // Test + // + // Test // // S // F00oo - $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + // F⭐⭐ + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\nF⭐\t\177\177⭐\t\n"); $dialog = new QuestionHelper(); $helperSet = new HelperSet([new FormatterHelper()]); $dialog->setHelperSet($helperSet); $question = new Question('Please select a bundle', 'FrameworkBundle'); - $question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle']); + $question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle', 'F⭐Y']); $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); @@ -197,6 +198,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('F⭐Y', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } public function testAskWithAutocompleteWithNonSequentialKeys() @@ -680,12 +682,13 @@ class QuestionHelperTest extends AbstractQuestionHelperTest // Acm // AcsTest // - // - // Test + // + // Test // // S // F00oo - $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + // F⭐⭐ + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\nF⭐\t⭐\t\n"); $dialog = new QuestionHelper(); $dialog->setInputStream($inputStream); @@ -693,7 +696,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest $dialog->setHelperSet($helperSet); $question = new Question('Please select a bundle', 'FrameworkBundle'); - $question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle']); + $question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle', 'F⭐Y']); $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); @@ -703,6 +706,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('F⭐Y', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } /** From b9b3fd89a32b1ef52a25ac0e58bce1f8172996bf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 29 Nov 2019 14:59:03 +0100 Subject: [PATCH 05/17] Fix CI --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b21a71d972..b1f4265d25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 != $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 @@ -283,10 +288,6 @@ install: echo "$COMPONENTS" | parallel --gnu "tfold {} 'cd {} && ([ -e composer.lock ] && ${COMPOSER_UP/update/install} || $COMPOSER_UP --prefer-lowest --prefer-stable) && $PHPUNIT_X'" echo "$COMPONENTS" | xargs -n1 -I{} tar --append -f ~/php-ext/composer-lowest.lock.tar {}/composer.lock else - 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 From a1ce0ed08602917d76810dbc2471dc25e28bda48 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 29 Nov 2019 15:40:35 +0100 Subject: [PATCH 06/17] fix dumping number-like string parameters --- .../DependencyInjection/Dumper/XmlDumper.php | 5 +++++ .../Tests/Fixtures/containers/container8.php | 11 +++++++++++ .../Tests/Fixtures/php/services8.php | 11 +++++++++++ .../Tests/Fixtures/xml/services8.xml | 11 +++++++++++ .../Tests/Fixtures/yaml/services8.yml | 11 +++++++++++ 5 files changed, 49 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index cfc9328439..eff421ec4e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -304,6 +304,11 @@ class XmlDumper extends Dumper if (\in_array($value, ['null', 'true', 'false'], true)) { $element->setAttribute('type', 'string'); } + + if (\is_string($value) && (is_numeric($value) || preg_match('/^0b[01]*$/', $value) || preg_match('/^0x[0-9a-f]++$/i', $value))) { + $element->setAttribute('type', 'string'); + } + $text = $this->document->createTextNode(self::phpToXml($value)); $element->appendChild($text); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container8.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container8.php index 5b3c01c23c..edcd045eaa 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container8.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container8.php @@ -9,6 +9,17 @@ $container = new ContainerBuilder(new ParameterBag([ 'bar' => 'foo is %%foo bar', 'escape' => '@escapeme', 'values' => [true, false, null, 0, 1000.3, 'true', 'false', 'null'], + 'null string' => 'null', + 'string of digits' => '123', + 'string of digits prefixed with minus character' => '-123', + 'true string' => 'true', + 'false string' => 'false', + 'binary number string' => '0b0110', + 'numeric string' => '-1.2E2', + 'hexadecimal number string' => '0xFF', + 'float string' => '10100.1', + 'positive float string' => '+10100.1', + 'negative float string' => '-10100.1', ])); return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php index ce4815ef81..e7a0214a10 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php @@ -151,6 +151,17 @@ class ProjectServiceContainer extends Container 6 => 'false', 7 => 'null', ], + 'null string' => 'null', + 'string of digits' => '123', + 'string of digits prefixed with minus character' => '-123', + 'true string' => 'true', + 'false string' => 'false', + 'binary number string' => '0b0110', + 'numeric string' => '-1.2E2', + 'hexadecimal number string' => '0xFF', + 'float string' => '10100.1', + 'positive float string' => '+10100.1', + 'negative float string' => '-10100.1', ]; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml index d0f9015c5a..4b07bbb7da 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services8.xml @@ -18,6 +18,17 @@ false null + null + 123 + -123 + true + false + 0b0110 + -1.2E2 + 0xFF + 10100.1 + +10100.1 + -10100.1 diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services8.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services8.yml index 4e37bc9315..002b1d4bcd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services8.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services8.yml @@ -4,6 +4,17 @@ parameters: bar: 'foo is %%foo bar' escape: '@@escapeme' values: [true, false, null, 0, 1000.3, 'true', 'false', 'null'] + null string: 'null' + string of digits: '123' + string of digits prefixed with minus character: '-123' + true string: 'true' + false string: 'false' + binary number string: '0b0110' + numeric string: '-1.2E2' + hexadecimal number string: '0xFF' + float string: '10100.1' + positive float string: '+10100.1' + negative float string: '-10100.1' services: service_container: From 8de2a226a8de220bacbe1981f972c560c37d4e29 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Sat, 23 Nov 2019 20:59:48 +0100 Subject: [PATCH 07/17] [Config][ReflectionClassResource] Handle parameters with undefined constant as their default values --- .../Resource/ReflectionClassResource.php | 50 +++++++++++++++++-- .../Resource/ReflectionClassResourceTest.php | 18 +++++-- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php index d5e6b829cf..4c8f89cd3f 100644 --- a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php +++ b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php @@ -151,12 +151,56 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali } } else { foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { - yield preg_replace('/^ @@.*/m', '', $m); - $defaults = []; + $parametersWithUndefinedConstants = []; foreach ($m->getParameters() as $p) { - $defaults[$p->name] = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null; + if (!$p->isDefaultValueAvailable()) { + $defaults[$p->name] = null; + + continue; + } + + if (!$p->isDefaultValueConstant() || \defined($p->getDefaultValueConstantName())) { + $defaults[$p->name] = $p->getDefaultValue(); + + continue; + } + + $defaults[$p->name] = $p->getDefaultValueConstantName(); + $parametersWithUndefinedConstants[$p->name] = true; } + + if (!$parametersWithUndefinedConstants) { + yield preg_replace('/^ @@.*/m', '', $m); + } else { + $stack = [ + $m->getDocComment(), + $m->getName(), + $m->isAbstract(), + $m->isFinal(), + $m->isStatic(), + $m->isPublic(), + $m->isPrivate(), + $m->isProtected(), + $m->returnsReference(), + \PHP_VERSION_ID >= 70000 && $m->hasReturnType() ? (\PHP_VERSION_ID >= 70100 ? $m->getReturnType()->getName() : (string) $m->getReturnType()) : '', + ]; + + foreach ($m->getParameters() as $p) { + if (!isset($parametersWithUndefinedConstants[$p->name])) { + $stack[] = (string) $p; + } else { + $stack[] = $p->isOptional(); + $stack[] = \PHP_VERSION_ID >= 70000 && $p->hasType() ? (\PHP_VERSION_ID >= 70100 ? $p->getType()->getName() : (string) $p->getType()) : ''; + $stack[] = $p->isPassedByReference(); + $stack[] = \PHP_VERSION_ID >= 50600 ? $p->isVariadic() : ''; + $stack[] = $p->getName(); + } + } + + yield implode(',', $stack); + } + yield print_r($defaults, true); } } diff --git a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php index 76cad1433b..74ed6b3edc 100644 --- a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php @@ -63,8 +63,12 @@ class ReflectionClassResourceTest extends TestCase /** * @dataProvider provideHashedSignature */ - public function testHashedSignature($changeExpected, $changedLine, $changedCode) + public function testHashedSignature($changeExpected, $changedLine, $changedCode, $setContext = null) { + if ($setContext) { + $setContext(); + } + $code = <<<'EOPHP' /* 0*/ /* 1*/ class %s extends ErrorException @@ -82,7 +86,9 @@ class ReflectionClassResourceTest extends TestCase /*13*/ protected function prot($a = []) {} /*14*/ /*15*/ private function priv() {} -/*16*/ } +/*16*/ +/*17*/ public function ccc($bar = A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC) {} +/*18*/ } EOPHP; static $expectedSignature, $generateSignature; @@ -97,7 +103,9 @@ EOPHP; } $code = explode("\n", $code); - $code[$changedLine] = $changedCode; + if (null !== $changedCode) { + $code[$changedLine] = $changedCode; + } eval(sprintf(implode("\n", $code), $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); $signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); @@ -145,6 +153,10 @@ EOPHP; yield [0, 7, 'protected int $prot;']; yield [0, 9, 'private string $priv;']; } + + yield [1, 17, 'public function ccc($bar = 187) {}']; + yield [1, 17, 'public function ccc($bar = ANOTHER_ONE_THAT_WILL_NEVER_BE_DEFINED_CCCCCCCCC) {}']; + yield [1, 17, null, static function () { \define('A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC', 'foo'); }]; } public function testEventSubscriber() From d9c64764075cb37f83ee7271d60b622af8d482d0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 29 Nov 2019 17:55:58 +0100 Subject: [PATCH 08/17] [HttpClient] remove conflict rule with HttpKernel that prevents using the component in Symfony 3.4 --- src/Symfony/Component/HttpClient/composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index be158f3296..68169d9da9 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -36,9 +36,6 @@ "symfony/http-kernel": "^4.4", "symfony/process": "^4.2|^5.0" }, - "conflict": { - "symfony/http-kernel": "<4.4" - }, "autoload": { "psr-4": { "Symfony\\Component\\HttpClient\\": "" }, "exclude-from-classmap": [ From b2ae60a73bb59c24da09ce8678290d939791ed0b Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Fri, 29 Nov 2019 20:07:18 +0100 Subject: [PATCH 09/17] [Validator] Update Slovenian translations --- .../Resources/translations/validators.sl.xlf | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf index 6f5fd98ca1..cb12a8a9da 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf @@ -318,6 +318,54 @@ Error Napaka + + This is not a valid UUID. + To ni veljaven UUID. + + + This value should be a multiple of {{ compared_value }}. + Ta vrednost bi morala biti večkratnik od {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ta poslovna identifikacijska koda (BIC) ni povezana z IBAN {{ iban }}. + + + This value should be valid JSON. + Ta vrednost bi morala biti veljaven JSON. + + + This collection should contain only unique elements. + Ta zbirka bi morala vsebovati samo edinstvene elemente. + + + This value should be positive. + Ta vrednost bi morala biti pozitivna. + + + This value should be either positive or zero. + Ta vrednost bi morala biti pozitivna ali enaka nič. + + + This value should be negative. + Ta vrednost bi morala biti negativna. + + + This value should be either negative or zero. + Ta vrednost bi morala biti negativna ali enaka nič. + + + This value is not a valid timezone. + Ta vrednost ni veljaven časovni pas. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + To geslo je ušlo pri kršitvi varnosti podatkov in ga ne smete uporabljati. Prosimo, uporabite drugo geslo. + + + This value should be between {{ min }} and {{ max }}. + Ta vrednost bi morala biti med {{ min }} in {{ max }}. + From d625a7370530423fed7ea262aaa14405b8f2e643 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 28 Nov 2019 04:41:34 +0100 Subject: [PATCH 10/17] [Security] Fix clearing remember-me cookie after deauthentication --- .../Security/Factory/RememberMeFactory.php | 6 +- .../DependencyInjection/SecurityExtension.php | 10 ++- .../Tests/Functional/ClearRememberMeTest.php | 77 +++++++++++++++++++ .../app/ClearRememberMe/bundles.php | 18 +++++ .../Functional/app/ClearRememberMe/config.yml | 32 ++++++++ .../app/ClearRememberMe/routing.yml | 7 ++ .../Bundle/SecurityBundle/composer.json | 2 +- .../Http/Firewall/ContextListener.php | 11 +++ .../Tests/Firewall/ContextListenerTest.php | 20 ++++- 9 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/bundles.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/routing.yml diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index 34de3d6701..8d419c0edd 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -81,7 +81,11 @@ class RememberMeFactory implements SecurityFactoryInterface 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)]) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 795fe053e6..19e9beb1e3 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -374,6 +374,7 @@ class SecurityExtension extends Extension $listeners[] = new Reference('security.channel_listener'); $contextKey = null; + $contextListenerId = null; // Context serializer listener if (false === $firewall['stateless']) { $contextKey = $id; @@ -390,7 +391,7 @@ class SecurityExtension extends Extension } $this->logoutOnUserChangeByContextKey[$contextKey] = [$id, $logoutOnUserChange]; - $listeners[] = new Reference($this->createContextListener($container, $contextKey, $logoutOnUserChange)); + $listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey, $logoutOnUserChange)); $sessionStrategyId = 'security.authentication.session_strategy'; } else { $this->statelessFirewallKeys[] = $id; @@ -463,7 +464,7 @@ class SecurityExtension extends Extension $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); @@ -519,7 +520,7 @@ class SecurityExtension extends Extension return $this->contextListeners[$contextKey] = $listenerId; } - private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider = null, array $providerIds, $defaultEntryPoint) + private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider = null, array $providerIds, $defaultEntryPoint, $contextListenerId = null) { $listeners = []; $hasListeners = false; @@ -537,6 +538,9 @@ class SecurityExtension extends Extension } elseif ('remember_me' === $key) { // RememberMeFactory will use the firewall secret when created $userProvider = null; + if ($contextListenerId) { + $container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']); + } } else { $userProvider = $defaultProvider ?: $this->getFirstProvider($id, $key, $providerIds); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php new file mode 100644 index 0000000000..3a1046b0c4 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php @@ -0,0 +1,77 @@ + + * + * 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; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +class ClearRememberMeTest extends AbstractWebTestCase +{ + public function testUserChangeClearsCookie() + { + $client = $this->createClient(['test_case' => 'ClearRememberMe', 'root_config' => 'config.yml']); + + $client->request('POST', '/login', [ + '_username' => 'johannes', + '_password' => 'test', + ]); + + $this->assertSame(302, $client->getResponse()->getStatusCode()); + $cookieJar = $client->getCookieJar(); + $this->assertNotNull($cookieJar->get('REMEMBERME')); + + $client->request('GET', '/foo'); + $this->assertSame(200, $client->getResponse()->getStatusCode()); + $this->assertNull($cookieJar->get('REMEMBERME')); + } +} + +class RememberMeFooController +{ + public function __invoke(UserInterface $user) + { + return new Response($user->getUsername()); + } +} + +class RememberMeUserProvider implements UserProviderInterface +{ + private $inner; + + public function __construct(InMemoryUserProvider $inner) + { + $this->inner = $inner; + } + + public function loadUserByUsername($username) + { + return $this->inner->loadUserByUsername($username); + } + + public function refreshUser(UserInterface $user) + { + $user = $this->inner->refreshUser($user); + + $alterUser = \Closure::bind(function (User $user) { $user->password = 'foo'; }, null, User::class); + $alterUser($user); + + return $user; + } + + public function supportsClass($class) + { + return $this->inner->supportsClass($class); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/bundles.php new file mode 100644 index 0000000000..9a26fb163a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\SecurityBundle\SecurityBundle; + +return [ + new FrameworkBundle(), + new SecurityBundle(), +]; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml new file mode 100644 index 0000000000..e5cefd37df --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/config.yml @@ -0,0 +1,32 @@ +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 + remember_me: + always_remember_me: true + secret: key + anonymous: ~ + logout_on_user_change: true + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeUserProvider: + public: true + decorates: security.user.provider.concrete.in_memory + arguments: ['@Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeUserProvider.inner'] diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/routing.yml new file mode 100644 index 0000000000..08975bdcb3 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ClearRememberMe/routing.yml @@ -0,0 +1,7 @@ +login: + path: /login + +foo: + path: /foo + defaults: + _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\RememberMeFooController diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 1f0e56e6ee..1a8057b6fb 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -19,7 +19,7 @@ "php": "^5.5.9|>=7.0.8", "ext-xml": "*", "symfony/config": "~3.4|~4.0", - "symfony/security": "~3.4.15|~4.0.15|^4.1.4", + "symfony/security": "~3.4.36|~4.3.9|^4.4.1", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-php70": "~1.0" diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index c6b89793e6..ea9f51f922 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -27,6 +27,7 @@ use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Role\SwitchUserRole; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; /** * ContextListener manages the SecurityContext persistence through a session. @@ -44,6 +45,7 @@ class ContextListener implements ListenerInterface private $registered; private $trustResolver; private $logoutOnUserChange = false; + private $rememberMeServices; /** * @param iterable|UserProviderInterface[] $userProviders @@ -103,6 +105,10 @@ class ContextListener implements ListenerInterface if ($token instanceof TokenInterface) { $token = $this->refreshUser($token); + + if (!$token && $this->logoutOnUserChange && $this->rememberMeServices) { + $this->rememberMeServices->loginFail($request); + } } elseif (null !== $token) { if (null !== $this->logger) { $this->logger->warning('Expected a security token from the session, got something else.', ['key' => $this->sessionKey, 'received' => $token]); @@ -268,4 +274,9 @@ class ContextListener implements ListenerInterface { throw new \UnexpectedValueException('Class not found: '.$class, 0x37313bc); } + + public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices) + { + $this->rememberMeServices = $rememberMeServices; + } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index 25915f212a..acab7087cb 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -31,6 +31,7 @@ use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Firewall\ContextListener; +use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; class ContextListenerTest extends TestCase { @@ -278,6 +279,19 @@ class ContextListenerTest extends TestCase $this->assertSame($goodRefreshedUser, $tokenStorage->getToken()->getUser()); } + public function testRememberMeGetsCanceledIfTokenIsDeauthenticated() + { + $tokenStorage = new TokenStorage(); + $refreshedUser = new User('foobar', 'baz'); + + $rememberMeServices = $this->createMock(RememberMeServicesInterface::class); + $rememberMeServices->expects($this->once())->method('loginFail'); + + $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], null, true, $rememberMeServices); + + $this->assertNull($tokenStorage->getToken()); + } + public function testTryAllUserProvidersUntilASupportingUserProviderIsFound() { $tokenStorage = new TokenStorage(); @@ -347,7 +361,7 @@ class ContextListenerTest extends TestCase return $session; } - private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, $userProviders, UserInterface $user = null, $logoutOnUserChange = false) + private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, $userProviders, UserInterface $user = null, $logoutOnUserChange = false, RememberMeServicesInterface $rememberMeServices = null) { $user = $user ?: new User('foo', 'bar'); $session = new Session(new MockArraySessionStorage()); @@ -359,6 +373,10 @@ class ContextListenerTest extends TestCase $listener = new ContextListener($tokenStorage, $userProviders, 'context_key'); $listener->setLogoutOnUserChange($logoutOnUserChange); + + if ($rememberMeServices) { + $listener->setRememberMeServices($rememberMeServices); + } $listener->handle(new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST)); } } From 7f803bc6743a80a2799c19de15dc27d3387f69c3 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 29 Nov 2019 13:21:45 +0100 Subject: [PATCH 11/17] Fix the translation commands when a template contains a syntax error --- .../Tests/Translation/TwigExtractorTest.php | 23 ++++++------------- .../Bridge/Twig/Translation/TwigExtractor.php | 9 +------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index 82d7298365..28932d9449 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -16,7 +16,6 @@ use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Translation\TwigExtractor; use Symfony\Component\Translation\MessageCatalogue; use Twig\Environment; -use Twig\Error\Error; use Twig\Loader\ArrayLoader; class TwigExtractorTest extends TestCase @@ -78,23 +77,15 @@ class TwigExtractorTest extends TestCase /** * @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('Symfony\Component\Translation\TranslatorInterface')->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()); } /** @@ -103,9 +94,9 @@ class TwigExtractorTest extends TestCase public function resourcesWithSyntaxErrorsProvider() { 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'), []], ]; } diff --git a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php index b7c7872266..107d8cc4bf 100644 --- a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +++ b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php @@ -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 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface 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 } } } From 4b9b93f5d661915b0d5f239e3447f53061e42bf6 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 26 Nov 2019 22:03:03 +0100 Subject: [PATCH 12/17] [Messenger] add tests to FailedMessagesShowCommand --- .../Command/FailedMessagesShowCommandTest.php | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php index f632d9890b..ae9b39a312 100644 --- a/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php +++ b/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Messenger\Stamp\RedeliveryStamp; use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp; use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp; use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface; +use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; /** * @group time-sensitive @@ -94,4 +95,101 @@ EOF $redeliveryStamp2->getRedeliveredAt()->format('Y-m-d H:i:s')), $tester->getDisplay(true)); } + + public function testReceiverShouldBeListable() + { + $receiver = $this->createMock(ReceiverInterface::class); + $command = new FailedMessagesShowCommand( + 'failure_receiver', + $receiver + ); + + $this->expectExceptionMessage('The "failure_receiver" receiver does not support listing or showing specific messages.'); + + $tester = new CommandTester($command); + $tester->execute(['id' => 15]); + } + + public function testListMessages() + { + $sentToFailureStamp = new SentToFailureTransportStamp('async'); + $redeliveryStamp = new RedeliveryStamp(0, 'failure_receiver', 'Things are bad!'); + $envelope = new Envelope(new \stdClass(), [ + new TransportMessageIdStamp(15), + $sentToFailureStamp, + $redeliveryStamp, + ]); + $receiver = $this->createMock(ListableReceiverInterface::class); + $receiver->expects($this->once())->method('all')->with()->willReturn([$envelope]); + + $command = new FailedMessagesShowCommand( + 'failure_receiver', + $receiver + ); + + $tester = new CommandTester($command); + $tester->execute([]); + $this->assertStringContainsString(sprintf(<<getRedeliveredAt()->format('Y-m-d H:i:s')), + $tester->getDisplay(true)); + } + + public function testListMessagesReturnsNoMessagesFound() + { + $receiver = $this->createMock(ListableReceiverInterface::class); + $receiver->expects($this->once())->method('all')->with()->willReturn([]); + + $command = new FailedMessagesShowCommand( + 'failure_receiver', + $receiver + ); + + $tester = new CommandTester($command); + $tester->execute([]); + $this->assertStringContainsString('[OK] No failed messages were found.', $tester->getDisplay(true)); + } + + public function testListMessagesReturnsPaginatedMessages() + { + $sentToFailureStamp = new SentToFailureTransportStamp('async'); + $envelope = new Envelope(new \stdClass(), [ + new TransportMessageIdStamp(15), + $sentToFailureStamp, + new RedeliveryStamp(0, 'failure_receiver', 'Things are bad!'), + ]); + $receiver = $this->createMock(ListableReceiverInterface::class); + $receiver->expects($this->once())->method('all')->with()->willReturn([$envelope]); + + $command = new FailedMessagesShowCommand( + 'failure_receiver', + $receiver + ); + + $tester = new CommandTester($command); + $tester->execute(['--max' => 1]); + $this->assertStringContainsString('Showing first 1 messages.', $tester->getDisplay(true)); + } + + public function testInvalidMessagesThrowsException() + { + $sentToFailureStamp = new SentToFailureTransportStamp('async'); + $envelope = new Envelope(new \stdClass(), [ + new TransportMessageIdStamp(15), + $sentToFailureStamp, + ]); + $receiver = $this->createMock(ListableReceiverInterface::class); + + $command = new FailedMessagesShowCommand( + 'failure_receiver', + $receiver + ); + + $this->expectExceptionMessage('The message "15" was not found.'); + + $tester = new CommandTester($command); + $tester->execute(['id' => 15]); + } } From b20ebe6b908cadca5c37040f305a101ce8cdf981 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 26 Nov 2019 18:28:34 +0100 Subject: [PATCH 13/17] [Security/Http] call auth listeners/guards eagerly when they "support" the request --- UPGRADE-4.4.md | 1 + UPGRADE-5.0.md | 2 +- .../SecurityBundle/Debug/WrappedListener.php | 2 +- .../DependencyInjection/SecurityExtension.php | 4 +- .../Resources/config/security.xml | 2 - .../Security/LazyFirewallContext.php | 60 +++++----- .../GuardedBundle/AppCustomAuthenticator.php | 59 ++++++++++ .../Tests/Functional/GuardedTest.php | 24 ++++ .../Tests/Functional/app/Guarded/bundles.php | 15 +++ .../Tests/Functional/app/Guarded/config.yml | 22 ++++ .../Tests/Functional/app/Guarded/routing.yml | 5 + .../Bundle/SecurityBundle/composer.json | 2 +- src/Symfony/Component/Security/CHANGELOG.md | 1 + .../Firewall/GuardAuthenticationListener.php | 52 ++++++--- .../Component/Security/Guard/composer.json | 2 +- .../Component/Security/Http/Firewall.php | 2 +- .../AbstractAuthenticationListener.php | 16 ++- .../Http/Firewall/AbstractListener.php | 42 +++++++ .../AbstractPreAuthenticatedListener.php | 25 ++-- .../Security/Http/Firewall/AccessListener.php | 22 +++- .../AnonymousAuthenticationListener.php | 13 ++- .../Firewall/BasicAuthenticationListener.php | 12 +- .../Http/Firewall/ChannelListener.php | 28 +++-- .../Http/Firewall/ContextListener.php | 12 +- .../Security/Http/Firewall/LogoutListener.php | 16 ++- .../Http/Firewall/RememberMeListener.php | 13 ++- .../SimplePreAuthenticationListener.php | 32 +++-- .../Http/Firewall/SwitchUserListener.php | 29 +++-- ...namePasswordJsonAuthenticationListener.php | 21 ++-- .../Tests/Firewall/AccessListenerTest.php | 54 ++------- .../AnonymousAuthenticationListenerTest.php | 8 +- .../Tests/Firewall/RememberMeListenerTest.php | 109 +++++------------- 32 files changed, 463 insertions(+), 244 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml create mode 100644 src/Symfony/Component/Security/Http/Firewall/AbstractListener.php diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 4bb7edc5d9..92847de89b 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -219,6 +219,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** diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 8a0b3ebe27..9fa32d9131 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -434,7 +434,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. diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php index 36b01fda12..0bc7fdda9e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php @@ -50,7 +50,7 @@ final class WrappedListener implements ListenerInterface if (\is_callable($this->listener)) { ($this->listener)($event); } else { - @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($this->listener)), E_USER_DEPRECATED); + @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, extend "%s" instead.', \get_class($this->listener), AbstractListener::class), E_USER_DEPRECATED); $this->listener->handle($event); } $this->time = microtime(true) - $startTime; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 627d7b92d5..97cbc824be 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -409,9 +409,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface } // 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'])); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 410646d9ba..2ea2c3fa7d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -156,9 +156,7 @@ - - diff --git a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php index ef9b1e217c..a45cc9c6d6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php @@ -13,11 +13,8 @@ namespace Symfony\Bundle\SecurityBundle\Security; 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 @@ use Symfony\Component\Security\Http\Firewall\LogoutListener; */ 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,26 +41,41 @@ class LazyFirewallContext extends FirewallContext public function __invoke(RequestEvent $event) { - $this->tokenStorage->setInitializer(function () use ($event) { - $event = new LazyResponseEvent($event); - foreach (parent::getListeners() as $listener) { - if (\is_callable($listener)) { - $listener($event); - } else { - @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($listener)), E_USER_DEPRECATED); - $listener->handle($event); + $listeners = []; + $request = $event->getRequest(); + $lazy = $request->isMethodCacheable(); + + foreach (parent::getListeners() as $listener) { + if (!\is_callable($listener)) { + @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, extend "%s" instead.', \get_class($listener), AbstractListener::class), E_USER_DEPRECATED); + $listeners[] = [$listener, 'handle']; + $lazy = false; + } elseif (!$lazy || !$listener instanceof AbstractListener) { + $listeners[] = $listener; + $lazy = $lazy && $listener instanceof AbstractListener; + } elseif (false !== $supports = $listener->supports($request)) { + $listeners[] = [$listener, 'authenticate']; + $lazy = null === $supports; + } + } + + if (!$lazy) { + foreach ($listeners as $listener) { + $listener($event); + + if ($event->hasResponse()) { + return; } } - }); - try { - [$attributes] = $this->map->getPatterns($event->getRequest()); - - if ($attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes) { - ($this->accessListener)($event); - } - } 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); + } + }); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php new file mode 100644 index 0000000000..fef2732759 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php @@ -0,0 +1,59 @@ + + * + * 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() + { + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php new file mode 100644 index 0000000000..bb0969c36a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php @@ -0,0 +1,24 @@ + + * + * 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; + +class GuardedTest extends AbstractWebTestCase +{ + public function testGuarded() + { + $client = $this->createClient(['test_case' => 'Guarded', 'root_config' => 'config.yml']); + + $client->request('GET', '/'); + + $this->assertSame(418, $client->getResponse()->getStatusCode()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php new file mode 100644 index 0000000000..d1e9eb7e0d --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new Symfony\Bundle\SecurityBundle\SecurityBundle(), +]; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml new file mode 100644 index 0000000000..2d1f779a53 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml @@ -0,0 +1,22 @@ +framework: + secret: test + router: { resource: "%kernel.project_dir%/%kernel.test_case%/routing.yml" } + test: ~ + default_locale: en + profiler: false + session: + storage_id: session.storage.mock_file + +services: + logger: { class: Psr\Log\NullLogger } + Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator: ~ + +security: + firewalls: + secure: + pattern: ^/ + anonymous: lazy + stateless: false + guard: + authenticators: + - Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml new file mode 100644 index 0000000000..4d11154375 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml @@ -0,0 +1,5 @@ +main: + path: / + defaults: + _controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction + path: /app diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 4ec8665479..4093fe2b94 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -24,7 +24,7 @@ "symfony/security-core": "^4.4", "symfony/security-csrf": "^4.2|^5.0", "symfony/security-guard": "^4.2|^5.0", - "symfony/security-http": "^4.4" + "symfony/security-http": "^4.4.1" }, "require-dev": { "doctrine/doctrine-bundle": "^1.5|^2.0", diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index cc30a5608f..0877880d88 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -14,6 +14,7 @@ CHANGELOG * Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. * Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` * Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.) + * Added `AbstractListener` which replaces the deprecated `ListenerInterface` 4.3.0 ----- diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index bcfa30dc58..83f0c5d2bc 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; +use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\Firewall\LegacyListenerTrait; use Symfony\Component\Security\Http\Firewall\ListenerInterface; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; @@ -33,7 +34,7 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; * * @final since Symfony 4.3 */ -class GuardAuthenticationListener implements ListenerInterface +class GuardAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -62,9 +63,9 @@ class GuardAuthenticationListener implements ListenerInterface } /** - * Iterates over each authenticator to see if each wants to authenticate the request. + * {@inheritdoc} */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { if (null !== $this->logger) { $context = ['firewall_key' => $this->providerKey]; @@ -76,7 +77,39 @@ class GuardAuthenticationListener implements ListenerInterface $this->logger->debug('Checking for guard authentication credentials.', $context); } + $guardAuthenticators = []; + foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { + if (null !== $this->logger) { + $this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); + } + + if ($guardAuthenticator->supports($request)) { + $guardAuthenticators[$key] = $guardAuthenticator; + } elseif (null !== $this->logger) { + $this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); + } + } + + if (!$guardAuthenticators) { + return false; + } + + $request->attributes->set('_guard_authenticators', $guardAuthenticators); + + return true; + } + + /** + * Iterates over each authenticator to see if each wants to authenticate the request. + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + $guardAuthenticators = $request->attributes->get('_guard_authenticators'); + $request->attributes->remove('_guard_authenticators'); + + foreach ($guardAuthenticators as $key => $guardAuthenticator) { // get a key that's unique to *this* guard authenticator // this MUST be the same as GuardAuthenticationProvider $uniqueGuardKey = $this->providerKey.'_'.$key; @@ -97,19 +130,6 @@ class GuardAuthenticationListener implements ListenerInterface { $request = $event->getRequest(); try { - if (null !== $this->logger) { - $this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); - } - - // abort the execution of the authenticator if it doesn't support the request - if (!$guardAuthenticator->supports($request)) { - if (null !== $this->logger) { - $this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); - } - - return; - } - if (null !== $this->logger) { $this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); } diff --git a/src/Symfony/Component/Security/Guard/composer.json b/src/Symfony/Component/Security/Guard/composer.json index af3ce94a9b..09b30d11ef 100644 --- a/src/Symfony/Component/Security/Guard/composer.json +++ b/src/Symfony/Component/Security/Guard/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "symfony/security-core": "^3.4.22|^4.2.3|^5.0", - "symfony/security-http": "^4.3" + "symfony/security-http": "^4.4.1" }, "require-dev": { "psr/log": "~1.0" diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index 08d4873c28..ee769496a6 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php @@ -138,7 +138,7 @@ class Firewall implements EventSubscriberInterface if (\is_callable($listener)) { $listener($event); } else { - @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($listener)), E_USER_DEPRECATED); + @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, extend "%s" instead.', \get_class($listener), AbstractListener::class), E_USER_DEPRECATED); $listener->handle($event); } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php index fed7785a61..736c247e7d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php @@ -49,7 +49,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; * @author Fabien Potencier * @author Johannes M. Schmitt */ -abstract class AbstractAuthenticationListener implements ListenerInterface +abstract class AbstractAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -105,20 +105,24 @@ abstract class AbstractAuthenticationListener implements ListenerInterface $this->rememberMeServices = $rememberMeServices; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return $this->requiresAuthentication($request); + } + /** * Handles form based authentication. * * @throws \RuntimeException * @throws SessionUnavailableException */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); - if (!$this->requiresAuthentication($request)) { - return; - } - if (!$request->hasSession()) { throw new \RuntimeException('This authentication method requires a session.'); } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php new file mode 100644 index 0000000000..ecbfa30233 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * A base class for listeners that can tell whether they should authenticate incoming requests. + * + * @author Nicolas Grekas + */ +abstract class AbstractListener +{ + final public function __invoke(RequestEvent $event) + { + if (false !== $this->supports($event->getRequest())) { + $this->authenticate($event); + } + } + + /** + * Tells whether the authenticate() method should be called or not depending on the incoming request. + * + * Returning null means authenticate() can be called lazily when accessing the token storage. + */ + abstract public function supports(Request $request): ?bool; + + /** + * Does whatever is required to authenticate the request, typically calling $event->setResponse() internally. + */ + abstract public function authenticate(RequestEvent $event); +} diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php index 500ae43e49..e14dd1a95a 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php @@ -35,7 +35,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; * * @internal since Symfony 4.3 */ -abstract class AbstractPreAuthenticatedListener implements ListenerInterface +abstract class AbstractPreAuthenticatedListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -56,20 +56,31 @@ abstract class AbstractPreAuthenticatedListener implements ListenerInterface } /** - * Handles pre-authentication. + * {@inheritdoc} */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); - try { - list($user, $credentials) = $this->getPreAuthenticatedData($request); + $request->attributes->set('_pre_authenticated_data', $this->getPreAuthenticatedData($request)); } catch (BadCredentialsException $e) { $this->clearToken($e); - return; + return false; } + return true; + } + + /** + * Handles pre-authentication. + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + + [$user, $credentials] = $request->attributes->get('_pre_authenticated_data'); + $request->attributes->remove('_pre_authenticated_data'); + if (null !== $this->logger) { $this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]); } diff --git a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php index 6164adde5d..00673f60ab 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Security\Http\Firewall; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Http\AccessMapInterface; @@ -27,7 +29,7 @@ use Symfony\Component\Security\Http\Event\LazyResponseEvent; * * @final since Symfony 4.3 */ -class AccessListener implements ListenerInterface +class AccessListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -44,13 +46,24 @@ class AccessListener implements ListenerInterface $this->authManager = $authManager; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + [$attributes] = $this->map->getPatterns($request); + $request->attributes->set('_access_control_attributes', $attributes); + + return $attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes ? true : null; + } + /** * Handles access authorization. * * @throws AccessDeniedException * @throws AuthenticationCredentialsNotFoundException */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (!$event instanceof LazyResponseEvent && null === $token = $this->tokenStorage->getToken()) { throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.'); @@ -58,9 +71,10 @@ class AccessListener implements ListenerInterface $request = $event->getRequest(); - list($attributes) = $this->map->getPatterns($request); + $attributes = $request->attributes->get('_access_control_attributes'); + $request->attributes->remove('_access_control_attributes'); - if (!$attributes) { + if (!$attributes || ([AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] === $attributes && $event instanceof LazyResponseEvent)) { return; } diff --git a/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php index b7a7381bfc..0f1da391e6 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Http\Firewall; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; @@ -26,7 +27,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; * * @final since Symfony 4.3 */ -class AnonymousAuthenticationListener implements ListenerInterface +class AnonymousAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -43,10 +44,18 @@ class AnonymousAuthenticationListener implements ListenerInterface $this->logger = $logger; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null; // always run authenticate() lazily with lazy firewalls + } + /** * Handles anonymous authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (null !== $this->tokenStorage->getToken()) { return; diff --git a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php index 9d6d81715c..dd18e87c5b 100644 --- a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php @@ -29,7 +29,7 @@ use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterfa * * @final since Symfony 4.3 */ -class BasicAuthenticationListener implements ListenerInterface +class BasicAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -55,10 +55,18 @@ class BasicAuthenticationListener implements ListenerInterface $this->ignoreFailure = false; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null !== $request->headers->get('PHP_AUTH_USER'); + } + /** * Handles basic authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); diff --git a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php index 671f279fdf..1033aa47ed 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Http\Firewall; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Http\AccessMapInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; @@ -24,7 +25,7 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface * * @final since Symfony 4.3 */ -class ChannelListener implements ListenerInterface +class ChannelListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -42,10 +43,8 @@ class ChannelListener implements ListenerInterface /** * Handles channel management. */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); - list(, $channel) = $this->map->getPatterns($request); if ('https' === $channel && !$request->isSecure()) { @@ -59,11 +58,7 @@ class ChannelListener implements ListenerInterface } } - $response = $this->authenticationEntryPoint->start($request); - - $event->setResponse($response); - - return; + return true; } if ('http' === $channel && $request->isSecure()) { @@ -71,9 +66,18 @@ class ChannelListener implements ListenerInterface $this->logger->info('Redirecting to HTTP.'); } - $response = $this->authenticationEntryPoint->start($request); - - $event->setResponse($response); + return true; } + + return false; + } + + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + + $response = $this->authenticationEntryPoint->start($request); + + $event->setResponse($response); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 4015262f01..2100968897 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -41,7 +41,7 @@ use Symfony\Component\Security\Http\Event\DeauthenticatedEvent; * * @final since Symfony 4.3 */ -class ContextListener implements ListenerInterface +class ContextListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -84,10 +84,18 @@ class ContextListener implements ListenerInterface @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), E_USER_DEPRECATED); } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null; // always run authenticate() lazily with lazy firewalls + } + /** * Reads the Security Token from the session. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (!$this->registered && null !== $this->dispatcher && $event->isMasterRequest()) { $this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']); diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index a53aeccf4a..e78f21826f 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -30,7 +30,7 @@ use Symfony\Component\Security\Http\ParameterBagUtils; * * @final since Symfony 4.3 */ -class LogoutListener implements ListenerInterface +class LogoutListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -63,6 +63,14 @@ class LogoutListener implements ListenerInterface $this->handlers[] = $handler; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return $this->requiresLogout($request); + } + /** * Performs the logout if requested. * @@ -72,14 +80,10 @@ class LogoutListener implements ListenerInterface * @throws LogoutException if the CSRF token is invalid * @throws \RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); - if (!$this->requiresLogout($request)) { - return; - } - if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); diff --git a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php index ebc03db862..0cfac54b34 100644 --- a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Firewall; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -31,7 +32,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; * * @final since Symfony 4.3 */ -class RememberMeListener implements ListenerInterface +class RememberMeListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -54,10 +55,18 @@ class RememberMeListener implements ListenerInterface $this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null; // always run authenticate() lazily with lazy firewalls + } + /** * Handles remember-me cookie based authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (null !== $this->tokenStorage->getToken()) { return; diff --git a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php index 2c444e823b..0641d9e45a 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php @@ -41,7 +41,7 @@ use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterfa * * @deprecated since Symfony 4.2, use Guard instead. */ -class SimplePreAuthenticationListener implements ListenerInterface +class SimplePreAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -79,10 +79,28 @@ class SimplePreAuthenticationListener implements ListenerInterface $this->sessionStrategy = $sessionStrategy; } + public function supports(Request $request): ?bool + { + if ((null !== $token = $this->tokenStorage->getToken()) && !$this->trustResolver->isAnonymous($token)) { + return false; + } + + $token = $this->simpleAuthenticator->createToken($request, $this->providerKey); + + // allow null to be returned to skip authentication + if (null === $token) { + return false; + } + + $request->attributes->set('_simple_pre_authenticator_token', $token); + + return true; + } + /** * Handles basic authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); @@ -91,16 +109,14 @@ class SimplePreAuthenticationListener implements ListenerInterface } if ((null !== $token = $this->tokenStorage->getToken()) && !$this->trustResolver->isAnonymous($token)) { + $request->attributes->remove('_simple_pre_authenticator_token'); + return; } try { - $token = $this->simpleAuthenticator->createToken($request, $this->providerKey); - - // allow null to be returned to skip authentication - if (null === $token) { - return; - } + $token = $request->attributes->get('_simple_pre_authenticator_token'); + $request->attributes->remove('_simple_pre_authenticator_token'); $token = $this->authenticationManager->authenticate($token); diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 4d546285f5..d762e5e429 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -39,7 +39,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; * * @final since Symfony 4.3 */ -class SwitchUserListener implements ListenerInterface +class SwitchUserListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -75,14 +75,10 @@ class SwitchUserListener implements ListenerInterface } /** - * Handles the switch to another user. - * - * @throws \LogicException if switching to a user failed + * {@inheritdoc} */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); - // usernames can be falsy $username = $request->get($this->usernameParameter); @@ -92,9 +88,26 @@ class SwitchUserListener implements ListenerInterface // if it's still "empty", nothing to do. if (null === $username || '' === $username) { - return; + return false; } + $request->attributes->set('_switch_user_username', $username); + + return true; + } + + /** + * Handles the switch to another user. + * + * @throws \LogicException if switching to a user failed + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + + $username = $request->attributes->get('_switch_user_username'); + $request->attributes->remove('_switch_user_username'); + if (null === $this->tokenStorage->getToken()) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 851e160beb..50eb405c61 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -44,7 +44,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; * * @final since Symfony 4.3 */ -class UsernamePasswordJsonAuthenticationListener implements ListenerInterface +class UsernamePasswordJsonAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -74,22 +74,27 @@ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } - /** - * {@inheritdoc} - */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); if (false === strpos($request->getRequestFormat(), 'json') && false === strpos($request->getContentType(), 'json') ) { - return; + return false; } if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) { - return; + return false; } + return true; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); $data = json_decode($request->getContent()); try { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php index 1dff48dfda..168e256437 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http\Tests\Firewall; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; @@ -26,7 +27,7 @@ class AccessListenerTest extends TestCase public function testHandleWhenTheAccessDecisionManagerDecidesToRefuseAccess() { $this->expectException('Symfony\Component\Security\Core\Exception\AccessDeniedException'); - $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap @@ -65,19 +66,12 @@ class AccessListenerTest extends TestCase $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWhenTheTokenIsNotAuthenticated() { - $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap @@ -136,19 +130,12 @@ class AccessListenerTest extends TestCase $authManager ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWhenThereIsNoAccessMapEntryMatchingTheRequest() { - $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap @@ -178,19 +165,12 @@ class AccessListenerTest extends TestCase $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWhenAccessMapReturnsEmptyAttributes() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock(); $accessMap @@ -213,12 +193,7 @@ class AccessListenerTest extends TestCase $this->getMockBuilder(AuthenticationManagerInterface::class)->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; + $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); $listener(new LazyResponseEvent($event)); } @@ -233,7 +208,7 @@ class AccessListenerTest extends TestCase ->willReturn(null) ; - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock(); $accessMap @@ -250,13 +225,6 @@ class AccessListenerTest extends TestCase $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php index 47f09199c4..e6f9f42217 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php @@ -12,7 +12,9 @@ namespace Symfony\Component\Security\Http\Tests\Firewall; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; @@ -38,7 +40,7 @@ class AnonymousAuthenticationListenerTest extends TestCase ; $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager); - $listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWithTokenStorageHavingNoToken() @@ -69,7 +71,7 @@ class AnonymousAuthenticationListenerTest extends TestCase ; $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager); - $listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST)); } public function testHandledEventIsLogged() @@ -84,6 +86,6 @@ class AnonymousAuthenticationListenerTest extends TestCase $authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(); $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', $logger, $authenticationManager); - $listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST)); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php index ceb557b139..d321ed6892 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Firewall\RememberMeListener; use Symfony\Component\Security\Http\SecurityEvents; @@ -27,7 +28,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()) ; @@ -45,7 +46,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage, $service) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -57,11 +58,6 @@ class RememberMeListenerTest extends TestCase ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $this->assertNull($listener($event)); } @@ -73,7 +69,7 @@ class RememberMeListenerTest extends TestCase $exception = new AuthenticationException('Authentication failed.'); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -96,12 +92,7 @@ class RememberMeListenerTest extends TestCase ->willThrowException($exception) ; - $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; + $event = $this->getGetResponseEvent($request); $listener($event); } @@ -113,7 +104,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, false); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -137,11 +128,6 @@ class RememberMeListenerTest extends TestCase ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $listener($event); } @@ -151,7 +137,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage, $service, $manager) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -174,11 +160,6 @@ class RememberMeListenerTest extends TestCase ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $listener($event); } @@ -188,7 +169,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage, $service, $manager) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -213,11 +194,6 @@ class RememberMeListenerTest extends TestCase ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $listener($event); } @@ -227,7 +203,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage, $service, $manager, , , $sessionStrategy) = $this->getListener(false, true, true); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -258,25 +234,10 @@ class RememberMeListenerTest extends TestCase ->willReturn(true) ; - $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->getMock(); - $request - ->expects($this->once()) - ->method('hasSession') - ->willReturn(true) - ; + $request = new Request(); + $request->setSession($session); - $request - ->expects($this->once()) - ->method('getSession') - ->willReturn($session) - ; - - $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; + $event = $this->getGetResponseEvent($request); $sessionStrategy ->expects($this->once()) @@ -292,7 +253,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, true, false); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -327,25 +288,10 @@ class RememberMeListenerTest extends TestCase ->method('migrate') ; - $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->getMock(); - $request - ->expects($this->any()) - ->method('hasSession') - ->willReturn(true) - ; + $request = new Request(); + $request->setSession($session); - $request - ->expects($this->any()) - ->method('getSession') - ->willReturn($session) - ; - - $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; + $event = $this->getGetResponseEvent($request); $listener($event); } @@ -355,7 +301,7 @@ class RememberMeListenerTest extends TestCase list($listener, $tokenStorage, $service, $manager, , $dispatcher) = $this->getListener(true); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -380,12 +326,6 @@ class RememberMeListenerTest extends TestCase ; $event = $this->getGetResponseEvent(); - $request = new Request(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; $dispatcher ->expects($this->once()) @@ -399,9 +339,20 @@ class RememberMeListenerTest extends TestCase $listener($event); } - protected function getGetResponseEvent() + protected function getGetResponseEvent(Request $request = null): RequestEvent { - return $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $request = $request ?? new Request(); + + $event = $this->getMockBuilder(RequestEvent::class) + ->setConstructorArgs([$this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST]) + ->getMock(); + $event + ->expects($this->any()) + ->method('getRequest') + ->willReturn($request) + ; + + return $event; } protected function getResponseEvent(): ResponseEvent From 6958d77f0cfd36e7a83cca0fcd5a7968a6c03c34 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 25 Nov 2019 16:00:32 +0100 Subject: [PATCH 14/17] do not depend on the QueryBuilder from the ORM --- .../Bridge/Doctrine/Form/Type/DoctrineType.php | 6 ++++-- src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php | 12 +++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index c5f34ce951..f25362d833 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -14,7 +14,6 @@ namespace Symfony\Bridge\Doctrine\Form\Type; 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; @@ -85,13 +84,16 @@ abstract class DoctrineType extends AbstractType implements ResetInterface * 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; } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 87d131a8c2..7d3f100663 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -53,6 +53,10 @@ class EntityType extends DoctrineType */ public function getLoader(ObjectManager $manager, $queryBuilder, $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); } @@ -68,11 +72,17 @@ class EntityType extends DoctrineType * 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()), From c3a658ac0fac76501a61c09f627cfe3fe4160783 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 29 Nov 2019 15:02:24 +0100 Subject: [PATCH 15/17] remove service when base class is missing --- .../DependencyInjection/Compiler/ExtensionPass.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index c6b0aaa584..76665764a1 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; +use Symfony\Bridge\Twig\Extension\AssetExtension; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -119,6 +120,10 @@ class ExtensionPass implements CompilerPassInterface $loader = $container->getDefinition('twig.loader.filesystem'); $loader->setMethodCalls(array_merge($twigLoader->getMethodCalls(), $loader->getMethodCalls())); + if (!method_exists(AssetExtension::class, 'getName')) { + $container->removeDefinition('templating.engine.twig'); + } + $twigLoader->clearTag('twig.loader'); } else { $container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false)); From 739357656d68e6bea35017c7f4683d438d391317 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 30 Nov 2019 13:15:01 +0100 Subject: [PATCH 16/17] [DI] fix overriding existing services with aliases for singly-implemented interfaces --- src/Symfony/Component/DependencyInjection/Loader/FileLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index a474613ef7..a02c0653ec 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -121,7 +121,7 @@ abstract class FileLoader extends BaseFileLoader public function registerAliasesForSinglyImplementedInterfaces() { foreach ($this->interfaces as $interface) { - if (!empty($this->singlyImplemented[$interface]) && !$this->container->hasAlias($interface)) { + if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) { $this->container->setAlias($interface, $this->singlyImplemented[$interface])->setPublic(false); } } From 209b330fd659cfeae02c4cbd5ba85a52332a71ea Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 30 Nov 2019 13:48:19 +0100 Subject: [PATCH 17/17] [DI] auto-register singly implemented interfaces by default --- .../Component/DependencyInjection/Loader/FileLoader.php | 5 +++++ .../DependencyInjection/Loader/PhpFileLoader.php | 2 ++ .../DependencyInjection/Loader/XmlFileLoader.php | 2 ++ .../DependencyInjection/Loader/YamlFileLoader.php | 2 ++ .../DependencyInjection/Tests/Loader/FileLoaderTest.php | 9 +++------ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index a474613ef7..dec8219b0a 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -36,6 +36,7 @@ abstract class FileLoader extends BaseFileLoader protected $instanceof = []; protected $interfaces = []; protected $singlyImplemented = []; + protected $autoRegisterAliasesForSinglyImplementedInterfaces = true; public function __construct(ContainerBuilder $container, FileLocatorInterface $locator) { @@ -116,6 +117,10 @@ abstract class FileLoader extends BaseFileLoader } } } + + if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) { + $this->registerAliasesForSinglyImplementedInterfaces(); + } } public function registerAliasesForSinglyImplementedInterfaces() diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index 0efa1239f0..f1477ecfd7 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -23,6 +23,8 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura */ class PhpFileLoader extends FileLoader { + protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 5733eb11ca..41a9f0a3ac 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -36,6 +36,8 @@ class XmlFileLoader extends FileLoader { const NS = 'http://symfony.com/schema/dic/services'; + protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 18a44b9806..ee8c140151 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -110,6 +110,8 @@ class YamlFileLoader extends FileLoader private $anonymousServicesCount; private $anonymousServicesSuffix; + protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 970cf416b3..e0e5928914 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -89,6 +89,7 @@ class FileLoaderTest extends TestCase $container = new ContainerBuilder(); $container->setParameter('sub_dir', 'Sub'); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + $loader->autoRegisterAliasesForSinglyImplementedInterfaces = false; $loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*'); $loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*'); // loading twice should not be an issue @@ -121,7 +122,6 @@ class FileLoaderTest extends TestCase // load everything, except OtherDir/AnotherSub & Foo.php 'Prototype/{%other_dir%/AnotherSub,Foo.php}' ); - $loader->registerAliasesForSinglyImplementedInterfaces(); $this->assertTrue($container->has(Bar::class)); $this->assertTrue($container->has(Baz::class)); @@ -151,7 +151,6 @@ class FileLoaderTest extends TestCase 'Prototype/OtherDir/AnotherSub/DeeperBaz.php', ] ); - $loader->registerAliasesForSinglyImplementedInterfaces(); $this->assertTrue($container->has(Foo::class)); $this->assertTrue($container->has(Baz::class)); @@ -167,7 +166,6 @@ class FileLoaderTest extends TestCase $prototype = new Definition(); $prototype->setPublic(true)->setPrivate(true); $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*'); - $loader->registerAliasesForSinglyImplementedInterfaces(); $this->assertTrue($container->has(Bar::class)); $this->assertTrue($container->has(Baz::class)); @@ -199,7 +197,6 @@ class FileLoaderTest extends TestCase 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\\', 'Prototype/%bad_classes_dir%/*' ); - $loader->registerAliasesForSinglyImplementedInterfaces(); $this->assertTrue($container->has(MissingParent::class)); @@ -218,7 +215,6 @@ class FileLoaderTest extends TestCase // the Sub is missing from namespace prefix $loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/Sub/*'); - $loader->registerAliasesForSinglyImplementedInterfaces(); } public function testRegisterClassesWithIncompatibleExclude() @@ -234,12 +230,13 @@ class FileLoaderTest extends TestCase 'Prototype/*', 'yaml/*' ); - $loader->registerAliasesForSinglyImplementedInterfaces(); } } class TestFileLoader extends FileLoader { + public $autoRegisterAliasesForSinglyImplementedInterfaces = true; + public function load($resource, $type = null) { return $resource;