diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index 70feb9ba3cab..68eceda5b974 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,18 @@ in 4.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1 +* 4.4.7 (2020-03-30) + + * security #cve-2020-5255 [HttpFoundation] Do not set the default Content-Type based on the Accept header (yceruto) + * security #cve-2020-5275 [Security] Fix access_control behavior with unanimous decision strategy (chalasr) + * bug #36262 [DI] fix generating TypedReference from PriorityTaggedServiceTrait (nicolas-grekas) + * bug #36252 [Security/Http] Allow setting cookie security settings for delete_cookies (wouterj) + * bug #36261 [FrameworkBundle] revert to legacy wiring of the session when circular refs are detected (nicolas-grekas) + * bug #36259 [DomCrawler] Fix BC break in assertions breaking Panther (dunglas) + * bug #36181 [BrowserKit] fixed missing post request parameters in file uploads (codebay) + * bug #36216 [Validator] Assert Valid with many groups (phucwan91) + * bug #36222 [Console] Fix OutputStream for PHP 7.4 (guillbdx) + * 4.4.6 (2020-03-27) * bug #36169 [HttpKernel] fix locking for PHP 7.4+ (nicolas-grekas) diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 03b7f6d2f8d1..66c108af35b7 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -103,7 +103,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $prevErrorHandler = set_error_handler(static function ($level, $message, $file, $line) use (&$prevErrorHandler) { if (E_USER_DEPRECATED === $level) { $templateLine = 0; - if (preg_match('/ at line (\d+) /', $message, $matches)) { + if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) { $templateLine = $matches[1]; } @@ -229,6 +229,14 @@ private function renderException(OutputInterface $output, string $template, Erro $output->text(sprintf(' ERROR (line %s)', $line)); } + // If the line is not known (this might happen for deprecations if we fail at detecting the line for instance), + // we render the message without context, to ensure the message is displayed. + if ($line <= 0) { + $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + + return; + } + foreach ($this->getContext($template, $line) as $lineNumber => $code) { $output->text(sprintf( '%s %-6s %s', diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index 890506840519..8970bb497764 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Console\Tester\CommandTester; use Twig\Environment; use Twig\Loader\FilesystemLoader; +use Twig\TwigFilter; class LintCommandTest extends TestCase { @@ -66,6 +67,37 @@ public function testLintFileCompileTimeException() $this->assertRegExp('/ERROR in \S+ \(line /', trim($tester->getDisplay())); } + /** + * When deprecations are not reported by the command, the testsuite reporter will catch them so we need to mark the test as legacy. + * + * @group legacy + */ + public function testLintFileWithNotReportedDeprecation() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile('{{ foo|deprecated_filter }}'); + + $ret = $tester->execute(['filename' => [$filename]], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString('OK in', trim($tester->getDisplay())); + } + + public function testLintFileWithReportedDeprecation() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile('{{ foo|deprecated_filter }}'); + + $ret = $tester->execute(['filename' => [$filename], '--show-deprecations' => true], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]); + + $this->assertEquals(1, $ret, 'Returns 1 in case of error'); + $this->assertRegExp('/ERROR in \S+ \(line 1\)/', trim($tester->getDisplay())); + $this->assertStringContainsString('Filter "deprecated_filter" is deprecated', trim($tester->getDisplay())); + } + + /** + * @group tty + */ public function testLintDefaultPaths() { $tester = $this->createCommandTester(); @@ -77,7 +109,12 @@ public function testLintDefaultPaths() private function createCommandTester(): CommandTester { - $command = new LintCommand(new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/'))); + $environment = new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/')); + $environment->addFilter(new TwigFilter('deprecated_filter', function ($v) { + return $v; + }, ['deprecated' => true])); + + $command = new LintCommand($environment); $application = new Application(); $application->add($command); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php index ad7194de97b0..f1f2bdc746e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Tests; +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\CustomPathBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php index 2f090b2de8d5..c58b25066bf4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Tests; +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\TestBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index f8dc8859efd4..34c6dc4297ee 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -177,7 +177,7 @@ private function sanitizeLogs(array $logs) continue; } - $message = $log['message']; + $message = '_'.$log['message']; $exception = $log['context']['exception']; if ($exception instanceof SilencedErrorContext) { diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php index adfba5d4220e..9c175397fced 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php @@ -176,13 +176,15 @@ public function getCollectTestData() [ ['message' => 'foo3', 'context' => ['exception' => new \ErrorException('warning', 0, E_USER_WARNING)], 'priority' => 100, 'priorityName' => 'DEBUG'], ['message' => 'foo3', 'context' => ['exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)], 'priority' => 100, 'priorityName' => 'DEBUG'], + ['message' => '0', 'context' => ['exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)], 'priority' => 100, 'priorityName' => 'DEBUG'], ], [ ['message' => 'foo3', 'context' => ['exception' => ['warning', E_USER_WARNING]], 'priority' => 100, 'priorityName' => 'DEBUG'], ['message' => 'foo3', 'context' => ['exception' => [E_USER_WARNING]], 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true], + ['message' => '0', 'context' => ['exception' => [E_USER_WARNING]], 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true], ], 0, - 1, + 2, ]; } } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php index e8f5ef4ce172..bfebfc5aa3f0 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport\Sender; +namespace Symfony\Component\Messenger\Tests\Transport\Receiver; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php index 021c7ae9706a..63aa43fb7ca0 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; +namespace Symfony\Component\Messenger\Tests\Transport\Sync; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\MessageBusInterface; diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php index 13549e27582a..8ea4eb2e0052 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; +namespace Symfony\Component\Messenger\Tests\Transport\Sync; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 3f48a1d828e1..0d3c32206786 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\PropertyInfo\Tests\PhpDocExtractor; +namespace Symfony\Component\PropertyInfo\Tests\Extractor; use phpDocumentor\Reflection\Types\Collection; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php index 7690b3e2264b..7a9c4d0cfb23 100644 --- a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php @@ -47,8 +47,10 @@ public function __construct(AccessDecisionManagerInterface $manager) /** * {@inheritdoc} + * + * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array */ - public function decide(TokenInterface $token, array $attributes, $object = null): bool + public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/): bool { $currentDecisionLog = [ 'attributes' => $attributes, @@ -58,7 +60,7 @@ public function decide(TokenInterface $token, array $attributes, $object = null) $this->currentLog[] = &$currentDecisionLog; - $result = $this->manager->decide($token, $attributes, $object); + $result = $this->manager->decide($token, $attributes, $object, 3 < \func_num_args() && func_get_arg(3)); $currentDecisionLog['result'] = $result; diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index d7eb4785a927..74ed391034f1 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Mapping\MetadataInterface; use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Util\PropertyPath; +use Symfony\Component\Validator\Validator\LazyProperty; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface; @@ -180,7 +181,7 @@ public function addViolation(string $message, array $parameters = []) $parameters, $this->root, $this->propertyPath, - $this->value, + $this->getValue(), null, null, $this->constraint @@ -199,7 +200,7 @@ public function buildViolation(string $message, array $parameters = []): Constra $parameters, $this->root, $this->propertyPath, - $this->value, + $this->getValue(), $this->translator, $this->translationDomain ); @@ -234,6 +235,10 @@ public function getRoot() */ public function getValue() { + if ($this->value instanceof LazyProperty) { + return $this->value->getPropertyValue(); + } + return $this->value; } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityWithGroupedConstraintOnMethods.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityWithGroupedConstraintOnMethods.php new file mode 100644 index 000000000000..89cae29f050c --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityWithGroupedConstraintOnMethods.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +class EntityWithGroupedConstraintOnMethods +{ + public $bar; + + public function isValidInFoo() + { + return false; + } + + public function getBar() + { + throw new \Exception('Should not be called'); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php index 954a62cb68e2..3d41539de2e6 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php @@ -14,14 +14,19 @@ use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Context\ExecutionContextFactory; +use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildA; use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildB; use Symfony\Component\Validator\Tests\Fixtures\Entity; +use Symfony\Component\Validator\Tests\Fixtures\EntityWithGroupedConstraintOnMethods; use Symfony\Component\Validator\Validator\RecursiveValidator; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -118,6 +123,25 @@ public function testCollectionConstraintValidateAllGroupsForNestedConstraints() $this->assertInstanceOf(NotBlank::class, $violations->get(1)->getConstraint()); } + public function testGroupedMethodConstraintValidateInSequence() + { + $metadata = new ClassMetadata(EntityWithGroupedConstraintOnMethods::class); + $metadata->addPropertyConstraint('bar', new NotNull(['groups' => 'Foo'])); + $metadata->addGetterMethodConstraint('validInFoo', 'isValidInFoo', new IsTrue(['groups' => 'Foo'])); + $metadata->addGetterMethodConstraint('bar', 'getBar', new NotNull(['groups' => 'Bar'])); + + $this->metadataFactory->addMetadata($metadata); + + $entity = new EntityWithGroupedConstraintOnMethods(); + $groups = new GroupSequence(['EntityWithGroupedConstraintOnMethods', 'Foo', 'Bar']); + + $violations = $this->validator->validate($entity, null, $groups); + + $this->assertCount(2, $violations); + $this->assertInstanceOf(NotNull::class, $violations->get(0)->getConstraint()); + $this->assertInstanceOf(IsTrue::class, $violations->get(1)->getConstraint()); + } + public function testAllConstraintValidateAllGroupsForNestedConstraints() { $this->metadata->addPropertyConstraint('data', new All(['constraints' => [ diff --git a/src/Symfony/Component/Validator/Validator/LazyProperty.php b/src/Symfony/Component/Validator/Validator/LazyProperty.php new file mode 100644 index 000000000000..a0799963c150 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/LazyProperty.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +/** + * A wrapper for a callable initializing a property from a getter. + * + * @internal + */ +class LazyProperty +{ + private $propertyValueCallback; + + public function __construct(\Closure $propertyValueCallback) + { + $this->propertyValueCallback = $propertyValueCallback; + } + + public function getPropertyValue() + { + return \call_user_func($this->propertyValueCallback); + } +} diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index 030b828e84d9..de724e8480ed 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -28,6 +28,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadataInterface; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Mapping\GenericMetadata; +use Symfony\Component\Validator\Mapping\GetterMetadata; use Symfony\Component\Validator\Mapping\MetadataInterface; use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Mapping\TraversalStrategy; @@ -502,7 +503,13 @@ private function validateClassNode(object $object, ?string $cacheKey, ClassMetad throw new UnsupportedMetadataException(sprintf('The property metadata instances should implement "Symfony\Component\Validator\Mapping\PropertyMetadataInterface", got: "%s".', \is_object($propertyMetadata) ? \get_class($propertyMetadata) : \gettype($propertyMetadata))); } - $propertyValue = $propertyMetadata->getPropertyValue($object); + if ($propertyMetadata instanceof GetterMetadata) { + $propertyValue = new LazyProperty(static function () use ($propertyMetadata, $object) { + return $propertyMetadata->getPropertyValue($object); + }); + } else { + $propertyValue = $propertyMetadata->getPropertyValue($object); + } $this->validateGenericNode( $propertyValue, @@ -729,6 +736,10 @@ private function validateInGroup($value, ?string $cacheKey, MetadataInterface $m $validator = $this->validatorFactory->getInstance($constraint); $validator->initialize($context); + if ($value instanceof LazyProperty) { + $value = $value->getPropertyValue(); + } + try { $validator->validate($value, $constraint); } catch (UnexpectedValueException $e) {