diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 94f0fabcc4676..b6f39741d9dbc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | master for features / 2.7 up to 4.0 for bug fixes +| Branch? | master for features / 2.8 up to 4.1 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | BC breaks? | no diff --git a/CHANGELOG-4.0.md b/CHANGELOG-4.0.md index 9abb4a61a6634..7131036ba4fa8 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -7,6 +7,47 @@ in 4.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.0.0...v4.0.1 +* 4.0.11 (2018-05-25) + + * bug #27364 [DI] Fix bad exception on uninitialized references to non-shared services (nicolas-grekas) + * bug #27359 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization (nicolas-grekas) + * security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured + * security #cve-2018-11406 clear CSRF tokens when the user is logged out + * security #cve-2018-11385 migrating session for UsernamePasswordJsonAuthenticationListener + * security #cve-2018-11385 Adding session authentication strategy to Guard to avoid session fixation + * security #cve-2018-11385 Adding session strategy to ALL listeners to avoid *any* possible fixation + * security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode + * bug #27341 [WebProfilerBundle] Fixed validator/dump trace CSS (yceruto) + * bug #27337 [FrameworkBundle] fix typo in CacheClearCommand (emilielorenzo) + +* 4.0.10 (2018-05-21) + + * bug #27264 [Validator] Use strict type in URL validator (mimol91) + * bug #27267 [DependencyInjection] resolve array env vars (jamesthomasonjr) + * bug #26781 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions on transform() (syastrebov) + * bug #27286 [Translation] Add Occitan plural rule (kylekatarnls) + * bug #27271 [DI] Allow defining bindings on ChildDefinition (nicolas-grekas) + * bug #27246 Disallow invalid characters in session.name (ostrolucky) + * bug #27287 [PropertyInfo] fix resolving parent|self type hints (nicolas-grekas) + * bug #27281 [HttpKernel] Fix dealing with self/parent in ArgumentMetadataFactory (fabpot) + * bug #24805 [Security] Fix logout (MatTheCat) + * bug #27265 [DI] Shared services should not be inlined in non-shared ones (nicolas-grekas) + * bug #27141 [Process] Suppress warnings when open_basedir is non-empty (cbj4074) + * bug #27250 [Session] limiting :key for GET_LOCK to 64 chars (oleg-andreyev) + * bug #27237 [Debug] Fix populating error_get_last() for handled silent errors (nicolas-grekas) + * bug #27232 [Cache][Lock] Fix usages of error_get_last() (nicolas-grekas) + * bug #27236 [Filesystem] Fix usages of error_get_last() (nicolas-grekas) + * bug #27191 [DI] Display previous error messages when throwing unused bindings (nicolas-grekas) + * bug #27231 [FrameworkBundle] Fix cache:clear on vagrant (nicolas-grekas) + * bug #27222 [WebProfilerBundle][Cache] Fix misses calculation when calling getItems (fsevestre) + * bug #27227 [HttpKernel] Handle NoConfigurationException "onKernelException()" (nicolas-grekas) + * bug #27152 [HttpFoundation] use brace-style regex delimiters (xabbuh) + * bug #27158 [Cache] fix logic for fetching tag versions on TagAwareAdapter (dmaicher) + * bug #27143 [Console] By default hide the short exception trace line from exception messages in Symfony's commands (yceruto) + * bug #27133 [Doctrine Bridge] fix priority for doctrine event listeners (dmaicher) + * bug #27135 [FrameworkBundle] Use the correct service id for CachePoolPruneCommand in its compiler pass (DemonTPx) + * feature #24896 Add CODE_OF_CONDUCT.md (egircys) + * 4.0.9 (2018-04-30) * bug #27074 [Debug][WebProfilerBundle] Fix setting file link format (lyrixx, nicolas-grekas) diff --git a/CHANGELOG-4.1.md b/CHANGELOG-4.1.md index 5437c76fd6bad..a042563276456 100644 --- a/CHANGELOG-4.1.md +++ b/CHANGELOG-4.1.md @@ -7,6 +7,30 @@ in 4.1 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.1.0...v4.1.1 +* 4.1.0-BETA3 (2018-05-26) + + * bug #27388 [Routing] Account for greediness when merging route patterns (nicolas-grekas) + * bug #27344 [HttpKernel] reset kernel start time on reboot (kiler129) + * bug #27365 [Serializer] Check the value of enable_max_depth if defined (dunglas) + * bug #27358 [PhpUnitBridge] silence some stderr outputs (ostrolucky) + * bug #27366 [DI] never inline lazy services (nicolas-grekas) + * bug #27352 Remove reference to the test container after kernel shutdown (stof) + * bug #27350 [HttpKernel] fix deprecation in AbstractTestSessionListener (alekitto) + * bug #27367 [FrameworkBundle] cleanup generated test container (nicolas-grekas) + * bug #27379 [FrameworkBundle] Fix using test.service_container when Client is rebooted (nicolas-grekas) + * bug #27364 [DI] Fix bad exception on uninitialized references to non-shared services (nicolas-grekas) + * bug #27359 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization (nicolas-grekas) + * security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured + * security #cve-2018-11406 clear CSRF tokens when the user is logged out + * security #cve-2018-11385 migrating session for UsernamePasswordJsonAuthenticationListener + * security #cve-2018-11385 migrating session for UsernamePasswordJsonAuthenticationListener + * security #cve-2018-11385 Adding session authentication strategy to Guard to avoid session fixation + * security #cve-2018-11385 Adding session strategy to ALL listeners to avoid *any* possible fixation + * security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode + * bug #27341 [WebProfilerBundle] Fixed validator/dump trace CSS (yceruto) + * bug #27337 [FrameworkBundle] fix typo in CacheClearCommand (emilielorenzo) + * bug #27292 [Serializer] Fix and improve constraintViolationListNormalizer's RFC7807 compliance (dunglas) + * 4.1.0-BETA2 (2018-05-21) * bug #27312 Supress deprecation notices thrown when getting private servies from container in tests (arderyp) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index bc4ebf6f78139..672246e7f9da8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -17,9 +17,9 @@ Symfony is the result of the work of many people who made the code better - Johannes S (johannes) - Jakub Zalas (jakubzalas) - Kris Wallsmith (kriswallsmith) + - Maxime Steinhausser (ogizanagi) - Ryan Weaver (weaverryan) - Javier Eguiluz (javier.eguiluz) - - Maxime Steinhausser (ogizanagi) - Grégoire Pineau (lyrixx) - Hugo Hamon (hhamon) - Abdellatif Ait boudad (aitboudad) @@ -35,17 +35,17 @@ Symfony is the result of the work of many people who made the code better - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) + - Samuel ROZE (sroze) - Jules Pietri (heah) - Eriksen Costa (eriksencosta) - Guilhem Niot (energetick) - Sarah Khalil (saro0h) - - Samuel ROZE (sroze) + - Yonel Ceruto (yonelceruto) - Jonathan Wage (jwage) - Hamza Amrouche (simperfit) - Diego Saint Esteben (dosten) - - Yonel Ceruto (yonelceruto) - - Alexandre Salomé (alexandresalome) - Iltar van der Berg (kjarli) + - Alexandre Salomé (alexandresalome) - William Durand (couac) - ornicar - Francis Besset (francisbesset) @@ -59,9 +59,9 @@ Symfony is the result of the work of many people who made the code better - Henrik Bjørnskov (henrikbjorn) - Dany Maillard (maidmaid) - Miha Vrhovnik + - Kevin Bond (kbond) - Tobias Nyholm (tobias) - Diego Saint Esteben (dii3g0) - - Kevin Bond (kbond) - Konstantin Kudryashov (everzet) - Alexander M. Turek (derrabus) - Bilal Amarni (bamarni) @@ -83,12 +83,12 @@ Symfony is the result of the work of many people who made the code better - Dariusz Górecki (canni) - Issei Murasawa (issei_m) - Douglas Greenshields (shieldo) + - David Maicher (dmaicher) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - Graham Campbell (graham) - Daniel Holmes (dholmes) - - David Maicher (dmaicher) - Dariusz Ruminski - Toni Uebernickel (havvg) - Bart van den Burg (burgov) @@ -103,9 +103,9 @@ Symfony is the result of the work of many people who made the code better - Maxime STEINHAUSSER - Michal Piotrowski (eventhorizon) - Tim Nagel (merk) + - Grégoire Paris (greg0ire) - Brice BERNARD (brikou) - Baptiste Clavié (talus) - - Grégoire Paris (greg0ire) - marc.weistroff - lenar - Alexander Schwenn (xelaris) @@ -139,21 +139,21 @@ Symfony is the result of the work of many people who made the code better - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) - Hidenori Goto (hidenorigoto) + - Jérôme Vasseur (jvasseur) + - Valentin Udaltsov (vudaltsov) + - gadelat (gadelat) - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - - Jérôme Vasseur (jvasseur) - Jérémie Augustin (jaugustin) - Andréia Bohner (andreia) - Philipp Wahala (hifi) - Julien Falque (julienfalque) - Rafael Dohms (rdohms) - Arnaud Kleinpeter (nanocom) - - gadelat (gadelat) - jwdeitch - Teoh Han Hui (teohhanhui) - Mikael Pajunen - Joel Wurtz (brouznouf) - - Valentin Udaltsov (vudaltsov) - Chris Wilkinson (thewilkybarkid) - Oleg Voronkovich - Vyacheslav Pavlov @@ -226,6 +226,7 @@ Symfony is the result of the work of many people who made the code better - Julien Brochet (mewt) - Leo Feyer - Tristan Darricau (nicofuma) + - Nikolay Labinskiy (e-moe) - Michaël Perrin (michael.perrin) - Marcel Beerta (mazen) - Loïc Faugeron @@ -260,6 +261,7 @@ Symfony is the result of the work of many people who made the code better - Kristen Gilden (kgilden) - Pierre-Yves LEBECQ (pylebecq) - Jordan Samouh (jordansamouh) + - Baptiste Lafontaine (magnetik) - Jakub Kucharovic (jkucharovic) - Uwe Jäger (uwej711) - Eugene Leonovich (rybakit) @@ -270,7 +272,6 @@ Symfony is the result of the work of many people who made the code better - Jan Sorgalla (jsor) - Ray - Tyson Andre - - Nikolay Labinskiy (e-moe) - Chekote - Thomas Adam - Albert Casademont (acasademont) @@ -286,6 +287,7 @@ Symfony is the result of the work of many people who made the code better - Oskar Stark (oskarstark) - Thomas Lallement (raziel057) - Giorgio Premi + - Christian Schmidt - Beau Simensen (simensen) - Michael Hirschler (mvhirsch) - Robert Kiss (kepten) @@ -317,7 +319,6 @@ Symfony is the result of the work of many people who made the code better - Jerzy Zawadzki (jzawadzki) - Wouter J - Ismael Ambrosi (iambrosi) - - Baptiste Lafontaine - François Pluchino (francoispluchino) - Aurelijus Valeiša (aurelijus) - Jan Decavele (jandc) @@ -361,6 +362,7 @@ Symfony is the result of the work of many people who made the code better - Yaroslav Kiliba - Terje Bråten - Mathieu Lechat + - MatTheCat - Robbert Klarenbeek (robbertkl) - JhonnyL - David Badura (davidbadura) @@ -424,7 +426,6 @@ Symfony is the result of the work of many people who made the code better - Jeanmonod David (jeanmonod) - Christopher Davis (chrisguitarguy) - Jan Schumann - - Christian Schmidt - Niklas Fiekas - Markus Bachmann (baachi) - lancergr @@ -437,6 +438,7 @@ Symfony is the result of the work of many people who made the code better - Josip Kruslin - Asmir Mustafic (goetas) - vagrant + - Aurimas Niekis (gcds) - EdgarPE - Florian Pfitzer (marmelatze) - Asier Illarramendi (doup) @@ -514,6 +516,7 @@ Symfony is the result of the work of many people who made the code better - De Cock Xavier (xdecock) - Almog Baku (almogbaku) - Scott Arciszewski + - Xavier HAUSHERR - Norbert Orzechowicz (norzechowicz) - Denis Charrier (brucewouaigne) - Matthijs van den Bos (matthijs) @@ -529,7 +532,6 @@ Symfony is the result of the work of many people who made the code better - Dawid Pakuła (zulusx) - Florian Rey (nervo) - Rodrigo Borrego Bernabé (rodrigobb) - - MatTheCat - Denis Gorbachev (starfall) - Peter van Dommelen - Tim van Densen @@ -563,6 +565,7 @@ Symfony is the result of the work of many people who made the code better - Mantas Var (mvar) - Sebastian Krebs - Jean-Christophe Cuvelier [Artack] + - Simon DELICATA - alcaeus - Fred Cox - vitaliytv @@ -580,6 +583,7 @@ Symfony is the result of the work of many people who made the code better - James Johnston - Sinan Eldem - Alexandre Dupuy (satchette) + - Malte Blättermann - Andre Rømcke (andrerom) - Nahuel Cuesta (ncuesta) - Chris Boden (cboden) @@ -604,7 +608,6 @@ Symfony is the result of the work of many people who made the code better - Michal Trojanowski - David Fuhr - Kamil Kokot (pamil) - - Aurimas Niekis (gcds) - Max Grigorian (maxakawizard) - mcfedr (mcfedr) - Rostyslav Kinash @@ -721,6 +724,7 @@ Symfony is the result of the work of many people who made the code better - Adam Szaraniec (mimol) - Yosmany Garcia (yosmanyga) - Wouter de Wild + - Antoine M (amakdessi) - Degory Valentine - izzyp - Benoit Lévêque (benoit_leveque) @@ -730,6 +734,7 @@ Symfony is the result of the work of many people who made the code better - Xavier Lacot (xavier) - possum - Denis Zunke (donalberto) + - Philipp Cordes - Ahmed TAILOULOUTE (ahmedtai) - Olivier Maisonneuve (olineuve) - Masterklavi @@ -749,7 +754,6 @@ Symfony is the result of the work of many people who made the code better - Adrien Lucas (adrienlucas) - Zhuravlev Alexander (scif) - James Michael DuPont - - Xavier HAUSHERR - Tom Klingenberg - Christopher Hall (mythmakr) - Patrick Dawkins (pjcdawkins) @@ -802,6 +806,7 @@ Symfony is the result of the work of many people who made the code better - corphi - grizlik - Derek ROTH + - Ben Johnson - Dmytro Boiko (eagle) - Shin Ohno (ganchiku) - Geert De Deckere (geertdd) @@ -882,6 +887,7 @@ Symfony is the result of the work of many people who made the code better - Michael Tibben - Billie Thompson - Sander Marechal + - Icode4Food (icode4food) - Radosław Benkel - jean pasqualini (darkilliant) - Ross Motley (rossmotley) @@ -993,6 +999,7 @@ Symfony is the result of the work of many people who made the code better - DerManoMann - Olaf Klischat - orlovv + - Jonathan Hedstrom - Peter Smeets (darkspartan) - Jhonny Lidfors (jhonny) - Julien Bianchi (jubianchi) @@ -1005,7 +1012,6 @@ Symfony is the result of the work of many people who made the code better - Andrew Tch - Alexander Cheprasov - Rodrigo Díez Villamuera (rodrigodiez) - - Malte Blättermann - e-ivanov - Jochen Bayer (jocl) - Alex Bowers @@ -1092,9 +1098,11 @@ Symfony is the result of the work of many people who made the code better - Tobias Stöckler - Mario Young - Ilia (aliance) + - Chris McCafferty (cilefen) - Grégoire Penverne (gpenverne) - Mo Di (modi) - Pablo Schläpfer + - Gert de Pagter - Jelte Steijaert (jelte) - Quique Porta (quiqueporta) - stoccc @@ -1177,9 +1185,9 @@ Symfony is the result of the work of many people who made the code better - Andreas Frömer - Philip Frank - Lance McNearney - - Antoine M (amakdessi) - Gonzalo Vilaseca (gonzalovilaseca) - Giorgio Premi + - ncou - Ian Carroll - caponica - Matt Daum (daum) @@ -1197,7 +1205,6 @@ Symfony is the result of the work of many people who made the code better - Tadcka - Beth Binkovitz - Gonzalo Míguez - - Philipp Cordes - Pierre Rineau - Romain Geissler - Adrien Moiruad @@ -1352,6 +1359,7 @@ Symfony is the result of the work of many people who made the code better - Pablo Maria Martelletti (pmartelletti) - Yassine Guedidi (yguedidi) - Waqas Ahmed + - Bert Hekman - Luis Muñoz - Matthew Donadio - Houziaux mike @@ -1450,6 +1458,7 @@ Symfony is the result of the work of many people who made the code better - Yannick Warnier (ywarnier) - Kevin Decherf - Jason Woods + - Oleg Andreyev - klemens - dened - Dmitry Korotovsky @@ -1508,6 +1517,7 @@ Symfony is the result of the work of many people who made the code better - Pierre Rineau - Maxim Lovchikov - adenkejawen + - Florent SEVESTRE (aniki-taicho) - Ari Pringle (apringle) - Dan Ordille (dordille) - Jan Eichhorn (exeu) @@ -1759,7 +1769,6 @@ Symfony is the result of the work of many people who made the code better - Matt Janssen - Ben Miller - Peter Gribanov - - Ben Johnson - kwiateusz - David Soria Parra - Sergiy Sokolenko @@ -1889,6 +1898,7 @@ Symfony is the result of the work of many people who made the code better - Julien Sanchez (sumbobyboys) - Guillermo Gisinger (t3chn0r) - Markus Tacker (tacker) + - Tarmo Leppänen (tarlepp) - Tyler Stroud (tystr) - Moritz Kraft (userfriendly) - Víctor Mateo (victormateo) diff --git a/README.md b/README.md index 9db99a74c0cc0..5796b1acd7ceb 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Community * [Join the Symfony Community][11] and meet other members at the [Symfony events][12]. * [Get Symfony support][13] on Stack Overflow, Slack, IRC, etc. * Follow us on [GitHub][14], [Twitter][15] and [Facebook][16]. +* Read our [Code of Conduct][24] and meet the [CARE Team][25] Contributing ------------ @@ -71,3 +72,5 @@ Symfony development is sponsored by [SensioLabs][21], led by the [21]: https://sensiolabs.com [22]: https://symfony.com/doc/current/contributing/code/core_team.html [23]: https://github.com/symfony/symfony-demo +[24]: https://symfony.com/coc +[25]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html diff --git a/phpunit b/phpunit index c0ffe8ddef9e9..f4b80ed064121 100755 --- a/phpunit +++ b/phpunit @@ -8,7 +8,7 @@ if (!file_exists(__DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit')) { exit(1); } if (\PHP_VERSION_ID >= 70000 && !getenv('SYMFONY_PHPUNIT_VERSION')) { - putenv('SYMFONY_PHPUNIT_VERSION=6.0'); + putenv('SYMFONY_PHPUNIT_VERSION=6.5'); } putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit'); require __DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit'; diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index f614ac99001b7..29b1960798b8c 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -109,30 +109,6 @@ public static function register($mode = 0) } $trace = debug_backtrace(true); - - // Silence deprecation warnings about private service accessed - // from the service container if done so from a Test class. - // As of Symfony 4.1, there is a new TestContainer that allows - // fetching of private services within tests, so we no longer - // need to warn about this behavior. - // - // NOTE: the event at the top of the stack $trace (index 0) should - // always be the PhpUnitBridge's DeprecationErrorHandler; the - // second event (index 1) should be the trigger_error() event; - // the third event (index 2) should be the actual source of the - // triggered deprecation notice; and the fourth event (index 3) - // represents the action that called the deprecated code. In the - // scenario that we want to suppress, the 4th event will be an - // object instance of \PHPUnit\Framework\TestCase. - if (isset($trace[3]['object'])) { - $isPrivateServiceNotice = false !== strpos($msg, ' service is private, '); - $isNoticeForContainerGetHasUsage = 'Symfony\Component\DependencyInjection\Container' === $trace[2]['class'] && in_array($trace[2]['function'], array('get', 'has')); - $noticeWasTriggeredByPhpUnitTest = $trace[3]['object'] instanceof \PHPUnit\Framework\TestCase; - if ($isPrivateServiceNotice && $isNoticeForContainerGetHasUsage && $noticeWasTriggeredByPhpUnitTest) { - return false; - } - } - $group = 'other'; $isVendor = DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $inVendors($file); diff --git a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php index 008ba437d83a9..134eefe940180 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php @@ -27,11 +27,11 @@ public function test() $dir = __DIR__.'/../Tests/Fixtures/coverage'; $phpunit = $_SERVER['argv'][0]; - exec("$php $phpunit -c $dir/phpunit-without-listener.xml.dist $dir/tests/ --coverage-text", $output); + exec("$php $phpunit -c $dir/phpunit-without-listener.xml.dist $dir/tests/ --coverage-text 2> /dev/null", $output); $output = implode("\n", $output); $this->assertContains('FooCov', $output); - exec("$php $phpunit -c $dir/phpunit-with-listener.xml.dist $dir/tests/ --coverage-text", $output); + exec("$php $phpunit -c $dir/phpunit-with-listener.xml.dist $dir/tests/ --coverage-text 2> /dev/null", $output); $output = implode("\n", $output); $this->assertNotContains('FooCov', $output); $this->assertContains("SutNotFoundTest::test\nCould not find the tested class.", $output); diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index 670a152a7c3bb..00ab83000a26f 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -77,7 +77,7 @@ if ('phpdbg' === PHP_SAPI) { $PHP .= ' -qrr'; } -$COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar`)) +$COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`)) ? $PHP.' '.escapeshellarg($COMPOSER) : 'composer'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Client.php b/src/Symfony/Bundle/FrameworkBundle/Client.php index 1499d050370aa..a5f6a1500bfbf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Client.php +++ b/src/Symfony/Bundle/FrameworkBundle/Client.php @@ -30,15 +30,15 @@ class Client extends BaseClient private $hasPerformedRequest = false; private $profiler = false; private $reboot = true; - private $container; + private $testContainerId; /** * {@inheritdoc} */ - public function __construct(KernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null, ContainerInterface $container = null) + public function __construct(KernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null, string $testContainerId = null) { parent::__construct($kernel, $server, $history, $cookieJar); - $this->container = $container; + $this->testContainerId = $testContainerId; } /** @@ -48,7 +48,9 @@ public function __construct(KernelInterface $kernel, array $server = array(), Hi */ public function getContainer() { - return $this->container ?? $this->kernel->getContainer(); + $container = $this->kernel->getContainer(); + + return null !== $this->testContainerId ? $container->get($this->testContainerId) : $container; } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index ee4edf31720ad..2127d5fbe94fa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -137,7 +137,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if ('/' === \DIRECTORY_SEPARATOR && $mounts = @file('/proc/mounts')) { foreach ($mounts as $mount) { $mount = array_slice(explode(' ', $mount), 1, -3); - if (!\in_array(array_pop($mount), array('vboxfs', 'nfs'))) { + if (!\in_array(array_pop($mount), array('vboxsf', 'nfs'))) { continue; } $mount = implode(' ', $mount).'/'; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php index 9e36a80d00ce6..19b36e3d2c843 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -27,13 +27,21 @@ public function process(ContainerBuilder $container) } $testContainer = $container->getDefinition('test.service_container'); - $privateContainer = $container->getDefinition((string) $testContainer->getArgument(2)); + $privateContainer = $testContainer->getArgument(2); + if ($privateContainer instanceof Reference) { + $privateContainer = $container->getDefinition((string) $privateContainer); + } $definitions = $container->getDefinitions(); + $privateServices = $privateContainer->getArgument(0); - foreach ($privateContainer->getArgument(0) as $id => $argument) { + foreach ($privateServices as $id => $argument) { if (isset($definitions[$target = (string) $argument->getValues()[0]])) { $argument->setValues(array(new Reference($target))); + } else { + unset($privateServices[$id]); } } + + $privateContainer->replaceArgument(0, $privateServices); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php index 62f17d64f1449..51fe553e19b2b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -31,7 +31,7 @@ public function process(ContainerBuilder $container) $definitions = $container->getDefinitions(); foreach ($definitions as $id => $definition) { - if ((!$definition->isPublic() || $definition->isPrivate()) && !$definition->getErrors() && !$definition->isAbstract()) { + if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate()) && !$definition->getErrors() && !$definition->isAbstract()) { $privateServices[$id] = new ServiceClosureArgument(new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE)); } } @@ -39,7 +39,7 @@ public function process(ContainerBuilder $container) $aliases = $container->getAliases(); foreach ($aliases as $id => $alias) { - if (!$alias->isPublic() || $alias->isPrivate()) { + if ($id && '.' !== $id[0] && (!$alias->isPublic() || $alias->isPrivate())) { while (isset($aliases[$target = (string) $alias])) { $alias = $aliases[$target]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml index d7aab2e068a58..f159208a41a28 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml @@ -16,7 +16,7 @@ %test.client.parameters% - + test.service_container diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index 8dfc292073ed8..351d27900e83a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -123,6 +123,7 @@ protected static function ensureKernelShutdown() $container->reset(); } } + static::$container = null; } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php index 0a7d2391d55e9..d73118a22a5c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php @@ -24,27 +24,24 @@ class AutowiringTypesTest extends WebTestCase public function testAnnotationReaderAutowiring() { static::bootKernel(array('root_config' => 'no_annotations_cache.yml', 'environment' => 'no_annotations_cache')); - $container = static::$kernel->getContainer(); - $annotationReader = $container->get('test.autowiring_types.autowired_services')->getAnnotationReader(); + $annotationReader = static::$container->get('test.autowiring_types.autowired_services')->getAnnotationReader(); $this->assertInstanceOf(AnnotationReader::class, $annotationReader); } public function testCachedAnnotationReaderAutowiring() { static::bootKernel(); - $container = static::$kernel->getContainer(); - $annotationReader = $container->get('test.autowiring_types.autowired_services')->getAnnotationReader(); + $annotationReader = static::$container->get('test.autowiring_types.autowired_services')->getAnnotationReader(); $this->assertInstanceOf(CachedReader::class, $annotationReader); } public function testTemplatingAutowiring() { static::bootKernel(); - $container = static::$kernel->getContainer(); - $autowiredServices = $container->get('test.autowiring_types.autowired_services'); + $autowiredServices = static::$container->get('test.autowiring_types.autowired_services'); $this->assertInstanceOf(FrameworkBundleEngineInterface::class, $autowiredServices->getFrameworkBundleEngine()); $this->assertInstanceOf(ComponentEngineInterface::class, $autowiredServices->getEngine()); } @@ -52,24 +49,21 @@ public function testTemplatingAutowiring() public function testEventDispatcherAutowiring() { static::bootKernel(array('debug' => false)); - $container = static::$kernel->getContainer(); - $autowiredServices = $container->get('test.autowiring_types.autowired_services'); + $autowiredServices = static::$container->get('test.autowiring_types.autowired_services'); $this->assertInstanceOf(EventDispatcher::class, $autowiredServices->getDispatcher(), 'The event_dispatcher service should be injected if the debug is not enabled'); static::bootKernel(array('debug' => true)); - $container = static::$kernel->getContainer(); - $autowiredServices = $container->get('test.autowiring_types.autowired_services'); + $autowiredServices = static::$container->get('test.autowiring_types.autowired_services'); $this->assertInstanceOf(TraceableEventDispatcher::class, $autowiredServices->getDispatcher(), 'The debug.event_dispatcher service should be injected if the debug is enabled'); } public function testCacheAutowiring() { static::bootKernel(); - $container = static::$kernel->getContainer(); - $autowiredServices = $container->get('test.autowiring_types.autowired_services'); + $autowiredServices = static::$container->get('test.autowiring_types.autowired_services'); $this->assertInstanceOf(FilesystemAdapter::class, $autowiredServices->getCachePool()); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php index 386a43424eae0..3a77541521200 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php @@ -77,9 +77,8 @@ public function testClearUnexistingPool() private function createCommandTester() { - $container = static::$kernel->getContainer(); $application = new Application(static::$kernel); - $application->add(new CachePoolClearCommand($container->get('cache.global_clearer'))); + $application->add(new CachePoolClearCommand(static::$container->get('cache.global_clearer'))); return new CommandTester($application->find('cache:pool:clear')); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php index eafc798a4838b..9cdb93a493f20 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php @@ -70,7 +70,7 @@ public function testRedisCustomCachePools() private function doTestCachePools($options, $adapterClass) { static::bootKernel($options); - $container = static::$kernel->getContainer(); + $container = static::$container; $pool1 = $container->get('cache.pool1'); $this->assertInstanceOf($adapterClass, $pool1); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php index 4bdf7592b45bc..21d9b1ca3b278 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php @@ -26,12 +26,12 @@ public function testDumpContainerIfNotExists() $application = new Application(static::$kernel); $application->setAutoExit(false); - @unlink(static::$kernel->getContainer()->getParameter('debug.container.dump')); + @unlink(static::$container->getParameter('debug.container.dump')); $tester = new ApplicationTester($application); $tester->run(array('command' => 'debug:container')); - $this->assertFileExists(static::$kernel->getContainer()->getParameter('debug.container.dump')); + $this->assertFileExists(static::$container->getParameter('debug.container.dump')); } public function testNoDebug() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php index bc7dc12ebfbca..bdcf462baea22 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php @@ -19,9 +19,8 @@ class SerializerTest extends WebTestCase public function testDeserializeArrayOfObject() { static::bootKernel(array('test_case' => 'Serializer')); - $container = static::$kernel->getContainer(); - $result = $container->get('serializer')->deserialize('{"bars": [{"id": 1}, {"id": 2}]}', Foo::class, 'json'); + $result = static::$container->get('serializer')->deserialize('{"bars": [{"id": 1}, {"id": 2}]}', Foo::class, 'json'); $bar1 = new Bar(); $bar1->id = 1; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php index 88d3b4cf29a31..b50c3e1f0b715 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php @@ -42,6 +42,6 @@ public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled() $this->assertTrue(static::$container->has(NonPublicService::class)); $this->assertTrue(static::$container->has(PrivateService::class)); $this->assertTrue(static::$container->has('private_service')); - $this->assertTrue(static::$container->has(UnusedPrivateService::class)); + $this->assertFalse(static::$container->has(UnusedPrivateService::class)); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php index 3dd18944de9f3..ba523382b66ba 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php @@ -26,7 +26,7 @@ class AddSessionDomainConstraintPass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) { + if (!$container->hasParameter('session.storage.options')) { return; } @@ -34,6 +34,7 @@ public function process(ContainerBuilder $container) $domainRegexp = empty($sessionOptions['cookie_domain']) ? '%s' : sprintf('(?:%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.'))); $domainRegexp = (empty($sessionOptions['cookie_secure']) ? 'https?://' : 'https://').$domainRegexp; + // if the service doesn't exist, an exception must be thrown - ignoring would put security at risk $container->findDefinition('security.http_utils')->addArgument(sprintf('{^%s$}i', $domainRegexp)); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php new file mode 100644 index 0000000000000..d4d28ecc4eb35 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.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\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Christian Flothmann + */ +class RegisterCsrfTokenClearingLogoutHandlerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) { + return; + } + + $csrfTokenStorage = $container->findDefinition('security.csrf.token_storage'); + $csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass()); + + if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) { + return; + } + + $container->register('security.logout.handler.csrf_token_clearing', 'Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler') + ->addArgument(new Reference('security.csrf.token_storage')) + ->setPublic(false); + + $container->findDefinition('security.logout_listener')->addMethodCall('addHandler', array(new Reference('security.logout.handler.csrf_token_clearing'))); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index da5d4c3559b68..dd6dd829d7494 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory; use Symfony\Component\Console\Application; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -58,7 +59,8 @@ public function build(ContainerBuilder $container) $extension->addUserProviderFactory(new InMemoryFactory()); $extension->addUserProviderFactory(new LdapFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); - $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass()); } public function registerCommands(Application $application) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php index 82205e5912969..8871a3346bbde 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php @@ -88,3 +88,10 @@ public function testVoterMissingInterface() $compilerPass->process($container); } } + +class VoterWithoutInterface +{ + public function vote() + { + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php index 382bdebe018fa..a836ab136cd93 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php @@ -96,6 +96,19 @@ public function testNoSession() $this->assertTrue($utils->createRedirectResponse($request, 'http://pirate.com/foo')->isRedirect('http://pirate.com/foo')); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException + * @expectedExceptionMessage You have requested a non-existent service "security.http_utils". + */ + public function testNoHttpUtils() + { + $container = new ContainerBuilder(); + $container->setParameter('session.storage.options', array()); + + $pass = new AddSessionDomainConstraintPass(); + $pass->process($container); + } + private function createContainer($sessionStorageOptions) { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php index a0bee3c01c1ca..25a70577b2c1e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php @@ -19,15 +19,13 @@ class AutowiringTypesTest extends WebTestCase public function testAccessDecisionManagerAutowiring() { static::bootKernel(array('debug' => false)); - $container = static::$kernel->getContainer(); - $autowiredServices = $container->get('test.autowiring_types.autowired_services'); + $autowiredServices = static::$container->get('test.autowiring_types.autowired_services'); $this->assertInstanceOf(AccessDecisionManager::class, $autowiredServices->getAccessDecisionManager(), 'The security.access.decision_manager service should be injected in debug mode'); static::bootKernel(array('debug' => true)); - $container = static::$kernel->getContainer(); - $autowiredServices = $container->get('test.autowiring_types.autowired_services'); + $autowiredServices = static::$container->get('test.autowiring_types.autowired_services'); $this->assertInstanceOf(TraceableAccessDecisionManager::class, $autowiredServices->getAccessDecisionManager(), 'The debug.security.access.decision_manager service should be injected in non-debug mode'); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php index 7eeb7c21171ce..d3c3b77fd5d61 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php @@ -31,4 +31,22 @@ public function testSessionLessRememberMeLogout() $this->assertNull($cookieJar->get('REMEMBERME')); } + + public function testCsrfTokensAreClearedOnLogout() + { + $client = $this->createClient(array('test_case' => 'LogoutWithoutSessionInvalidation', 'root_config' => 'config.yml')); + $client->getContainer()->get('security.csrf.token_storage')->setToken('foo', 'bar'); + + $client->request('POST', '/login', array( + '_username' => 'johannes', + '_password' => 'test', + )); + + $this->assertTrue($client->getContainer()->get('security.csrf.token_storage')->hasToken('foo')); + $this->assertSame('bar', $client->getContainer()->get('security.csrf.token_storage')->getToken('foo')); + + $client->request('GET', '/logout'); + + $this->assertFalse($client->getContainer()->get('security.csrf.token_storage')->hasToken('foo')); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php new file mode 100644 index 0000000000000..d90f774abde2b --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/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\SecurityBundle\SecurityBundle; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return array( + new FrameworkBundle(), + new SecurityBundle(), +); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml new file mode 100644 index 0000000000000..9e5563fea5197 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml @@ -0,0 +1,26 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + providers: + in_memory: + memory: + users: + johannes: { password: test, roles: [ROLE_USER] } + + firewalls: + default: + form_login: + check_path: login + remember_me: true + require_previous_session: false + remember_me: + always_remember_me: true + secret: secret + logout: + invalidate_session: false + anonymous: ~ + stateless: true diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/routing.yml new file mode 100644 index 0000000000000..1dddfca2f8154 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/routing.yml @@ -0,0 +1,5 @@ +login: + path: /login + +logout: + path: /logout diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 8a64bcea04c60..31ecaeebeb4a3 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -44,10 +44,10 @@ "twig/twig": "~1.34|~2.4" }, "conflict": { - "symfony/security": "4.1.0-beta1", + "symfony/security": "4.1.0-beta1|4.1.0-beta2", "symfony/var-dumper": "<3.4", "symfony/event-dispatcher": "<3.4", - "symfony/framework-bundle": "<4.1", + "symfony/framework-bundle": "<=4.1-beta2", "symfony/console": "<3.4" }, "autoload": { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig index f9bc41d6a1b54..4752f6ef28fbf 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -901,6 +901,7 @@ table.logs .metadata { background: #FFF; padding: 10px; margin: 0.5em 0; + overflow: auto; } #collector-content .sf-validator .trace { font-size: 12px; @@ -932,6 +933,7 @@ table.logs .metadata { background: #FFF; padding: 10px; margin: 0.5em 0; + overflow: auto; } #collector-content pre.sf-dump, diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php index 1bae24c10b458..b13f2bdc5f8b9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; @@ -31,9 +30,6 @@ protected function processValue($value, $isRoot = false) if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) { throw new ServiceNotFoundException($id, $this->currentId); } - if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() && $this->container->has($id = (string) $value) && !$this->container->findDefinition($id)->isShared()) { - throw new InvalidArgumentException(sprintf('Invalid ignore-on-uninitialized reference found in service "%s": target service "%s" is not shared.', $this->currentId, $id)); - } return $value; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index b153b84764bc1..9aee66c8e0d5b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -88,11 +88,15 @@ protected function processValue($value, $isRoot = false) */ private function isInlineableDefinition($id, Definition $definition, ServiceReferenceGraph $graph) { + if ($definition->getErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic()) { + return false; + } + if (!$definition->isShared()) { return true; } - if ($definition->isDeprecated() || $definition->isPublic() || $definition->isLazy() || $definition->getErrors()) { + if ($definition->isPublic()) { return false; } @@ -120,6 +124,6 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe return false; } - return $this->container->getDefinition($ids[0])->isShared(); + return !$ids || $this->container->getDefinition($ids[0])->isShared(); } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 8491f806b4b17..cfe9fdfcfb99f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -1660,6 +1660,9 @@ private function getServiceCall(string $id, Reference $reference = null): string if ($this->container->hasDefinition($id) && ($definition = $this->container->getDefinition($id)) && !$definition->isSynthetic()) { if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { $code = 'null'; + if (!$definition->isShared()) { + return $code; + } } elseif ($this->isTrivialInstance($definition)) { $code = substr($this->addNewInstance($definition, '', '', $id), 8, -2); if ($definition->isShared()) { diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php index 6e3d75bb0b9de..d258980d6660a 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php @@ -54,17 +54,4 @@ public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionPa return $prefix.$parent->name; } } - - private static function export($value) - { - if (!is_array($value)) { - return var_export($value, true); - } - $code = array(); - foreach ($value as $k => $v) { - $code[] = sprintf('%s => %s', var_export($k, true), self::export($v)); - } - - return sprintf('array(%s)', implode(', ', $code)); - } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php index ac002d834d1c5..a3fbfcf10132f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php @@ -68,27 +68,6 @@ public function testProcessThrowsExceptionOnInvalidReferenceFromInlinedDefinitio $this->process($container); } - /** - * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid ignore-on-uninitialized reference found in service - */ - public function testProcessThrowsExceptionOnNonSharedUninitializedReference() - { - $container = new ContainerBuilder(); - - $container - ->register('a', 'stdClass') - ->addArgument(new Reference('b', $container::IGNORE_ON_UNINITIALIZED_REFERENCE)) - ; - - $container - ->register('b', 'stdClass') - ->setShared(false) - ; - - $this->process($container); - } - public function testProcessDefinitionWithBindings() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 4e00b708dcca7..d08d163927611 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; -use DummyProxyDumper; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Symfony\Component\Config\FileLocator; @@ -530,6 +529,19 @@ public function testInlinedDefinitionReferencingServiceContainer() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services13.php', $dumper->dump(), '->dump() dumps inline definitions which reference service_container'); } + public function testNonSharedLazyDefinitionReferences() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setShared(false)->setLazy(true); + $container->register('bar', 'stdClass')->addArgument(new Reference('foo', ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, false))->setPublic(true); + $container->compile(); + + $dumper = new PhpDumper($container); + $dumper->setProxyDumper(new \DummyProxyDumper()); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_non_shared_lazy.php', $dumper->dump()); + } + public function testInitializePropertiesBeforeMethodCalls() { require_once self::$fixturesPath.'/includes/classes.php'; @@ -598,7 +610,7 @@ public function testCircularReferenceAllowanceForInlinedDefinitionsForLazyServic $dumper = new PhpDumper($container); - $dumper->setProxyDumper(new DummyProxyDumper()); + $dumper->setProxyDumper(new \DummyProxyDumper()); $dumper->dump(); $this->addToAssertionCount(1); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php index bc653a744c9c5..bced911043c55 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php @@ -85,17 +85,17 @@ class DummyProxyDumper implements ProxyDumper { public function isProxyCandidate(Definition $definition) { - return false; + return $definition->isLazy(); } public function getProxyFactoryCode(Definition $definition, $id, $factoryCall = null) { - return ''; + return " // lazy factory\n\n"; } public function getProxyCode(Definition $definition) { - return ''; + return "// proxy code\n"; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php new file mode 100644 index 0000000000000..84a1dd80b3dee --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php @@ -0,0 +1,90 @@ +services = $this->privates = array(); + $this->methodMap = array( + 'bar' => 'getBarService', + ); + + $this->aliases = array(); + } + + public function reset() + { + $this->privates = array(); + parent::reset(); + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function getRemovedIds() + { + return array( + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'foo' => true, + ); + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'bar' shared service. + * + * @return \stdClass + */ + protected function getBarService() + { + return $this->services['bar'] = new \stdClass($this->getFooService()); + } + + /** + * Gets the private 'foo' service. + * + * @return \stdClass + */ + protected function getFooService($lazyLoad = true) + { + // lazy factory + + return new \stdClass(); + } +} + +// proxy code diff --git a/src/Symfony/Component/Form/Test/Traits/ValidatorExtensionTrait.php b/src/Symfony/Component/Form/Test/Traits/ValidatorExtensionTrait.php index 0724f697ba77a..d5bcb04a4df6d 100644 --- a/src/Symfony/Component/Form/Test/Traits/ValidatorExtensionTrait.php +++ b/src/Symfony/Component/Form/Test/Traits/ValidatorExtensionTrait.php @@ -31,7 +31,7 @@ protected function getValidatorExtension() } $this->validator = $this->getMockBuilder(ValidatorInterface::class)->getMock(); - $metadata = $this->getMockBuilder(ClassMetadata::class)->disableOriginalConstructor()->getMock(); + $metadata = $this->getMockBuilder(ClassMetadata::class)->disableOriginalConstructor()->setMethods(array('addPropertyConstraint'))->getMock(); $this->validator->expects($this->any())->method('getMetadataFor')->will($this->returnValue($metadata)); $this->validator->expects($this->any())->method('validate')->will($this->returnValue(array())); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php index 1b77e1ff6accb..a2b4e2ef9fb6b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php @@ -22,6 +22,7 @@ public function test2Dot5ValidationApi() ->disableOriginalConstructor() ->getMock(); $metadata = $this->getMockBuilder('Symfony\Component\Validator\Mapping\ClassMetadata') + ->setMethods(array('addConstraint', 'addPropertyConstraint')) ->disableOriginalConstructor() ->getMock(); diff --git a/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php b/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php index e3ef45ef672cf..d78c76068234f 100644 --- a/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php +++ b/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php @@ -80,13 +80,8 @@ public static function reset() */ private function __construct() { - if (FileBinaryMimeTypeGuesser::isSupported()) { - $this->register(new FileBinaryMimeTypeGuesser()); - } - - if (FileinfoMimeTypeGuesser::isSupported()) { - $this->register(new FileinfoMimeTypeGuesser()); - } + $this->register(new FileBinaryMimeTypeGuesser()); + $this->register(new FileinfoMimeTypeGuesser()); } /** @@ -125,18 +120,14 @@ public function guess($path) throw new AccessDeniedException($path); } - if (!$this->guessers) { - $msg = 'Unable to guess the mime type as no guessers are available'; - if (!FileinfoMimeTypeGuesser::isSupported()) { - $msg .= ' (Did you enable the php_fileinfo extension?)'; - } - throw new \LogicException($msg); - } - foreach ($this->guessers as $guesser) { if (null !== $mimeType = $guesser->guess($path)) { return $mimeType; } } + + if (2 === \count($this->guessers) && !FileBinaryMimeTypeGuesser::isSupported() && !FileinfoMimeTypeGuesser::isSupported()) { + throw new \LogicException('Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)'); + } } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 43f07f25de1dd..0fd34dc1b65ff 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -616,6 +616,7 @@ protected function doRead($sessionId) $selectSql = $this->getSelectSql(); $selectStmt = $this->pdo->prepare($selectSql); $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt = null; do { $selectStmt->execute(); @@ -631,6 +632,11 @@ protected function doRead($sessionId) return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; } + if (null !== $insertStmt) { + $this->rollback(); + throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); + } + if (!ini_get('session.use_strict_mode') && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { // In strict mode, session fixation is not possible: new sessions always start with a unique // random id, so that concurrency is not possible and this code path can be skipped. diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/common.inc b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/common.inc index ba101d357852d..f9c40a9a3c5e1 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/common.inc +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/common.inc @@ -22,6 +22,10 @@ error_reporting(-1); ini_set('html_errors', 0); ini_set('display_errors', 1); +if (ini_get('xdebug.default_enable')) { + xdebug_disable(); +} + header_remove('X-Powered-By'); header('Content-Type: text/plain; charset=utf-8'); diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php index 82061fd6ea0fc..f07647f8a67dc 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php @@ -61,10 +61,12 @@ public function onKernelResponse(FilterResponseEvent $event) return; } - if (!$session = $event->getRequest()->getSession()) { + $request = $event->getRequest(); + if (!$request->hasSession()) { return; } + $session = $request->getSession(); if ($wasStarted = $session->isStarted()) { $session->save(); } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index ae3ea7243749f..4fbabf8d9bfcc 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -63,12 +63,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.1.0-BETA2'; + const VERSION = '4.1.0-BETA3'; const VERSION_ID = 40100; const MAJOR_VERSION = 4; const MINOR_VERSION = 1; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'BETA2'; + const EXTRA_VERSION = 'BETA3'; const END_OF_MAINTENANCE = '01/2019'; const END_OF_LIFE = '07/2019'; @@ -79,18 +79,10 @@ public function __construct(string $environment, bool $debug) $this->debug = $debug; $this->rootDir = $this->getRootDir(); $this->name = $this->getName(); - - if ($this->debug) { - $this->startTime = microtime(true); - } } public function __clone() { - if ($this->debug) { - $this->startTime = microtime(true); - } - $this->booted = false; $this->container = null; $this->requestStackSize = 0; @@ -108,10 +100,16 @@ public function boot() $this->container->get('services_resetter')->reset(); } $this->resetServices = false; + if ($this->debug) { + $this->startTime = microtime(true); + } } return; } + if ($this->debug) { + $this->startTime = microtime(true); + } if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) { putenv('SHELL_VERBOSITY=3'); $_ENV['SHELL_VERBOSITY'] = 3; diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php index 22a2b71239874..84ca04a680d61 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php @@ -123,6 +123,16 @@ public function testDoesNotImplementServiceSubscriberInterface() $this->assertFalse(is_subclass_of(TestSessionListener::class, ServiceSubscriberInterface::class, 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford')); } + public function testDoesNotThrowIfRequestDoesNotHaveASession() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, new Response()); + + $this->listener->onKernelResponse($event); + + $this->assertTrue(true); + } + private function filterResponse(Request $request, $type = HttpKernelInterface::MASTER_REQUEST) { $request->setSession($this->session); diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index 9dab664cf5cdf..5a302d3090cf9 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -589,6 +589,21 @@ public function testServicesResetter() $this->assertEquals(1, ResettableService::$counter); } + /** + * @group time-sensitive + */ + public function testKernelStartTimeIsResetWhileBootingAlreadyBootedKernel() + { + $kernel = $this->getKernelForTest(array('initializeBundles'), true); + $kernel->boot(); + $preReBoot = $kernel->getStartTime(); + + sleep(3600); //Intentionally large value to detect if ClockMock ever breaks + $kernel->reboot(null); + + $this->assertGreaterThan($preReBoot, $kernel->getStartTime()); + } + /** * Returns a mock for the BundleInterface. * @@ -658,10 +673,10 @@ protected function getKernel(array $methods = array(), array $bundles = array()) return $kernel; } - protected function getKernelForTest(array $methods = array()) + protected function getKernelForTest(array $methods = array(), $debug = false) { $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') - ->setConstructorArgs(array('test', false)) + ->setConstructorArgs(array('test', $debug)) ->setMethods($methods) ->getMock(); $p = new \ReflectionProperty($kernel, 'rootDir'); diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php index 05315defccdfc..cd9e6b4c3e7f3 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php @@ -151,6 +151,7 @@ private function getCommonPrefix(string $prefix, string $anotherPrefix): array $baseLength = \strlen($this->prefix); $end = min(\strlen($prefix), \strlen($anotherPrefix)); $staticLength = null; + set_error_handler(array(__CLASS__, 'handleError')); for ($i = $baseLength; $i < $end && $prefix[$i] === $anotherPrefix[$i]; ++$i) { if ('(' === $prefix[$i]) { @@ -174,13 +175,24 @@ private function getCommonPrefix(string $prefix, string $anotherPrefix): array if (('?' === ($prefix[$j] ?? '') || '?' === ($anotherPrefix[$j] ?? '')) && ($prefix[$j] ?? '') !== ($anotherPrefix[$j] ?? '')) { break; } + $subPattern = substr($prefix, $i, $j - $i); + if ($prefix !== $anotherPrefix && !preg_match('/^\(\[[^\]]++\]\+\+\)$/', $subPattern) && !preg_match('{(? array(array('_route' => 'a', '_locale' => 'en'), array('_locale'), null, null), 43 => array(array('_route' => 'b', '_locale' => 'en'), array('_locale'), null, null), - 58 => array(array('_route' => 'c', '_locale' => 'en'), array('_locale', 'id'), null, null), - 73 => array(array('_route' => 'd', '_locale' => 'en'), array('_locale', 'id'), null, null), - 86 => array(array('_route' => 'e', '_locale' => 'en'), array('_locale', 'id'), null, null), - 104 => array(array('_route' => 'f', '_locale' => 'en'), array('_locale'), null, null), - 120 => array(array('_route' => 'g', '_locale' => 'en'), array('_locale'), null, null), - 144 => array(array('_route' => 'h', '_locale' => 'en'), array('_locale', 'page'), null, null), - 165 => array(array('_route' => 'i', '_locale' => 'en'), array('_locale', 'page'), null, null), - 192 => array(array('_route' => 'j', '_locale' => 'en'), array('_locale', 'id'), null, null), - 206 => array(array('_route' => 'k', '_locale' => 'en'), array('_locale'), null, null), - 223 => array(array('_route' => 'l', '_locale' => 'en'), array('_locale'), null, null), - 234 => array(array('_route' => 'm', '_locale' => 'en'), array('_locale'), null, null), - 253 => array(array('_route' => 'n', '_locale' => 'en'), array('_locale'), null, null), + 55 => array(array('_route' => 'c', '_locale' => 'en'), array('_locale', 'id'), null, null), + 72 => array(array('_route' => 'd', '_locale' => 'en'), array('_locale', 'id'), null, null), + 91 => array(array('_route' => 'e', '_locale' => 'en'), array('_locale', 'id'), null, null), + 107 => array(array('_route' => 'f', '_locale' => 'en'), array('_locale'), null, null), + 123 => array(array('_route' => 'g', '_locale' => 'en'), array('_locale'), null, null), + 147 => array(array('_route' => 'h', '_locale' => 'en'), array('_locale', 'page'), null, null), + 168 => array(array('_route' => 'i', '_locale' => 'en'), array('_locale', 'page'), null, null), + 195 => array(array('_route' => 'j', '_locale' => 'en'), array('_locale', 'id'), null, null), + 209 => array(array('_route' => 'k', '_locale' => 'en'), array('_locale'), null, null), + 226 => array(array('_route' => 'l', '_locale' => 'en'), array('_locale'), null, null), + 237 => array(array('_route' => 'm', '_locale' => 'en'), array('_locale'), null, null), + 256 => array(array('_route' => 'n', '_locale' => 'en'), array('_locale'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -139,7 +135,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a return $ret; } - if (253 === $m) { + if (256 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 0ba61c948610f..b3bfd78271bf3 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -611,6 +611,16 @@ public function testRequirementWithCapturingGroup() $this->assertEquals(array('_route' => 'a', 'a' => 'a', 'b' => 'b'), $matcher->match('/a/b')); } + public function testDotAllWithCatchAll() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/{id}.html', array(), array('id' => '.+'))); + $coll->add('b', new Route('/{all}', array(), array('all' => '.+'))); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('_route' => 'a', 'id' => 'foo/bar'), $matcher->match('/foo/bar.html')); + } + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) { return new UrlMatcher($routes, $context ?: new RequestContext()); diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php index cdb4f8767a3ad..1b033cfacc685 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/Argon2iPasswordEncoderTest.php @@ -30,7 +30,7 @@ protected function setUp() public function testValidationWithConfig() { - $encoder = new Argon2iPasswordEncoder(4, 4, 1); + $encoder = new Argon2iPasswordEncoder(8, 4, 1); $result = $encoder->encodePassword(self::PASSWORD, null); $this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null)); $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); diff --git a/src/Symfony/Component/Security/Core/User/UserInterface.php b/src/Symfony/Component/Security/Core/User/UserInterface.php index 0a359d079da19..0ce8eca92aa4e 100644 --- a/src/Symfony/Component/Security/Core/User/UserInterface.php +++ b/src/Symfony/Component/Security/Core/User/UserInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\User; +use Symfony\Component\Security\Core\Role\Role; + /** * Represents the interface that all user classes must implement. * diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php index 66197c95ac2d2..c8d31093ceff2 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php @@ -113,4 +113,32 @@ public function testRemoveExistingToken() $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); $this->assertFalse($this->storage->hasToken('token_id')); } + + public function testClearRemovesAllTokensFromTheConfiguredNamespace() + { + $this->storage->setToken('foo', 'bar'); + $this->storage->clear(); + + $this->assertFalse($this->storage->hasToken('foo')); + $this->assertArrayNotHasKey(self::SESSION_NAMESPACE, $_SESSION); + } + + public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces() + { + $_SESSION['foo']['bar'] = 'baz'; + $this->storage->clear(); + + $this->assertArrayHasKey('foo', $_SESSION); + $this->assertArrayHasKey('bar', $_SESSION['foo']); + $this->assertSame('baz', $_SESSION['foo']['bar']); + } + + public function testClearDoesNotRemoveNonNamespacedSessionValues() + { + $_SESSION['foo'] = 'baz'; + $this->storage->clear(); + + $this->assertArrayHasKey('foo', $_SESSION); + $this->assertSame('baz', $_SESSION['foo']); + } } diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php index 306e19ad91bb9..7539852f13f3f 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php @@ -129,4 +129,31 @@ public function testRemoveExistingTokenFromActiveSession() $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); } + + public function testClearRemovesAllTokensFromTheConfiguredNamespace() + { + $this->storage->setToken('foo', 'bar'); + $this->storage->clear(); + + $this->assertFalse($this->storage->hasToken('foo')); + $this->assertFalse($this->session->has(self::SESSION_NAMESPACE.'/foo')); + } + + public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces() + { + $this->session->set('foo/bar', 'baz'); + $this->storage->clear(); + + $this->assertTrue($this->session->has('foo/bar')); + $this->assertSame('baz', $this->session->get('foo/bar')); + } + + public function testClearDoesNotRemoveNonNamespacedSessionValues() + { + $this->session->set('foo', 'baz'); + $this->storage->clear(); + + $this->assertTrue($this->session->has('foo')); + $this->assertSame('baz', $this->session->get('foo')); + } } diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php b/src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php new file mode 100644 index 0000000000000..0d6f16b68d0b6 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +/** + * @author Christian Flothmann + */ +interface ClearableTokenStorageInterface extends TokenStorageInterface +{ + /** + * Removes all CSRF tokens. + */ + public function clear(); +} diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php index d79e98d212c5d..aa59240be0820 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php @@ -18,7 +18,7 @@ * * @author Bernhard Schussek */ -class NativeSessionTokenStorage implements TokenStorageInterface +class NativeSessionTokenStorage implements ClearableTokenStorageInterface { /** * The namespace used to store values in the session. @@ -102,6 +102,14 @@ public function removeToken($tokenId) return $token; } + /** + * {@inheritdoc} + */ + public function clear() + { + unset($_SESSION[$this->namespace]); + } + private function startSession() { if (PHP_SESSION_NONE === session_status()) { diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php index a13f367b0ec30..67f93ec6724b0 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php @@ -19,7 +19,7 @@ * * @author Bernhard Schussek */ -class SessionTokenStorage implements TokenStorageInterface +class SessionTokenStorage implements ClearableTokenStorageInterface { /** * The namespace used to store values in the session. @@ -92,4 +92,16 @@ public function removeToken($tokenId) return $this->session->remove($this->namespace.'/'.$tokenId); } + + /** + * {@inheritdoc} + */ + public function clear() + { + foreach (array_keys($this->session->all()) as $key) { + if (0 === strpos($key, $this->namespace.'/')) { + $this->session->remove($key); + } + } + } } diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 0af68b16a56a9..3cdf1d12a45b0 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -48,6 +48,7 @@ public function __construct(TokenStorageInterface $tokenStorage, EventDispatcher */ public function authenticateWithToken(TokenInterface $token, Request $request) { + $this->migrateSession($request); $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { @@ -108,4 +109,12 @@ public function handleAuthenticationFailure(AuthenticationException $authenticat is_object($response) ? get_class($response) : gettype($response) )); } + + private function migrateSession(Request $request) + { + if (!$request->hasSession() || !$request->hasPreviousSession()) { + return; + } + $request->getSession()->migrate(true); + } } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php index 1057868ddb337..b933b4e5f8668 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php @@ -82,6 +82,9 @@ final public function handle(GetResponseEvent $event) if (null !== $this->logger) { $this->logger->info('Pre-authentication successful.', array('token' => (string) $token)); } + + $this->migrateSession($request); + $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { @@ -114,4 +117,12 @@ private function clearToken(AuthenticationException $exception) * @return array An array composed of the user and the credentials */ abstract protected function getPreAuthenticatedData(Request $request); + + private function migrateSession(Request $request) + { + if (!$request->hasSession() || !$request->hasPreviousSession()) { + return; + } + $request->getSession()->migrate(true); + } } diff --git a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php index f8a01ad405ab0..ca44fcdb4c730 100644 --- a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Http\Firewall; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; @@ -70,6 +71,9 @@ public function handle(GetResponseEvent $event) try { $token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey)); + + $this->migrateSession($request); + $this->tokenStorage->setToken($token); } catch (AuthenticationException $e) { $token = $this->tokenStorage->getToken(); @@ -88,4 +92,12 @@ public function handle(GetResponseEvent $event) $event->setResponse($this->authenticationEntryPoint->start($request, $e)); } } + + private function migrateSession(Request $request) + { + if (!$request->hasSession() || !$request->hasPreviousSession()) { + return; + } + $request->getSession()->migrate(true); + } } diff --git a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php index ec607bb4574be..c1f492b4e7aca 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Http\Firewall; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -77,6 +78,9 @@ public function handle(GetResponseEvent $event) } $token = $this->authenticationManager->authenticate($token); + + $this->migrateSession($request); + $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { @@ -111,4 +115,12 @@ public function handle(GetResponseEvent $event) } } } + + private function migrateSession(Request $request) + { + if (!$request->hasSession() || !$request->hasPreviousSession()) { + return; + } + $request->getSession()->migrate(true); + } } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 5a977b855ff2e..3707c8669901b 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -139,6 +139,8 @@ private function onSuccess(Request $request, TokenInterface $token) $this->logger->info('User has been authenticated successfully.', array('username' => $token->getUsername())); } + $this->migrateSession($request); + $this->tokenStorage->setToken($token); if (null !== $this->eventDispatcher) { @@ -182,4 +184,12 @@ private function onFailure(Request $request, AuthenticationException $failed) return $response; } + + private function migrateSession(Request $request) + { + if (!$request->hasSession() || !$request->hasPreviousSession()) { + return; + } + $request->getSession()->migrate(true); + } } diff --git a/src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php b/src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php new file mode 100644 index 0000000000000..ad6b888aad562 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Logout; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; + +/** + * @author Christian Flothmann + */ +class CsrfTokenClearingLogoutHandler implements LogoutHandlerInterface +{ + private $csrfTokenStorage; + + public function __construct(ClearableTokenStorageInterface $csrfTokenStorage) + { + $this->csrfTokenStorage = $csrfTokenStorage; + } + + public function logout(Request $request, Response $response, TokenInterface $token) + { + $this->csrfTokenStorage->clear(); + } +} diff --git a/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php b/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php index ed055f20949fc..c61fe349f8f42 100644 --- a/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php +++ b/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategy.php @@ -47,6 +47,8 @@ public function onAuthentication(Request $request, TokenInterface $token) return; case self::MIGRATE: + // Note: this logic is duplicated in several authentication listeners + // until Symfony 5.0 due to a security fix with BC compat $request->getSession()->migrate(true); return; diff --git a/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategyInterface.php b/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategyInterface.php index 9b05f151340ee..8de89b1868d16 100644 --- a/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategyInterface.php +++ b/src/Symfony/Component/Security/Http/Session/SessionAuthenticationStrategyInterface.php @@ -27,8 +27,8 @@ interface SessionAuthenticationStrategyInterface /** * This performs any necessary changes to the session. * - * This method is called before the TokenStorage is populated with a - * Token, and only by classes inheriting from AbstractAuthenticationListener. + * This method should be called before the TokenStorage is populated with a + * Token. It should be used by authentication listeners when a session is used. */ public function onAuthentication(Request $request, TokenInterface $token); } diff --git a/src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php new file mode 100644 index 0000000000000..fe34eaa6e5da3 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\Logout; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler; + +class CsrfTokenClearingLogoutHandlerTest extends TestCase +{ + private $session; + private $csrfTokenStorage; + private $csrfTokenClearingLogoutHandler; + + protected function setUp() + { + $this->session = new Session(new MockArraySessionStorage()); + $this->csrfTokenStorage = new SessionTokenStorage($this->session, 'foo'); + $this->csrfTokenStorage->setToken('foo', 'bar'); + $this->csrfTokenStorage->setToken('foobar', 'baz'); + $this->csrfTokenClearingLogoutHandler = new CsrfTokenClearingLogoutHandler($this->csrfTokenStorage); + } + + public function testCsrfTokenCookieWithSameNamespaceIsRemoved() + { + $this->assertSame('bar', $this->session->get('foo/foo')); + $this->assertSame('baz', $this->session->get('foo/foobar')); + + $this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()); + + $this->assertFalse($this->csrfTokenStorage->hasToken('foo')); + $this->assertFalse($this->csrfTokenStorage->hasToken('foobar')); + + $this->assertFalse($this->session->has('foo/foo')); + $this->assertFalse($this->session->has('foo/foobar')); + } + + public function testCsrfTokenCookieWithDifferentNamespaceIsNotRemoved() + { + $barNamespaceCsrfSessionStorage = new SessionTokenStorage($this->session, 'bar'); + $barNamespaceCsrfSessionStorage->setToken('foo', 'bar'); + $barNamespaceCsrfSessionStorage->setToken('foobar', 'baz'); + + $this->assertSame('bar', $this->session->get('foo/foo')); + $this->assertSame('baz', $this->session->get('foo/foobar')); + $this->assertSame('bar', $this->session->get('bar/foo')); + $this->assertSame('baz', $this->session->get('bar/foobar')); + + $this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()); + + $this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foo')); + $this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foobar')); + $this->assertSame('bar', $barNamespaceCsrfSessionStorage->getToken('foo')); + $this->assertSame('baz', $barNamespaceCsrfSessionStorage->getToken('foobar')); + $this->assertFalse($this->csrfTokenStorage->hasToken('foo')); + $this->assertFalse($this->csrfTokenStorage->hasToken('foobar')); + + $this->assertFalse($this->session->has('foo/foo')); + $this->assertFalse($this->session->has('foo/foobar')); + $this->assertSame('bar', $this->session->get('bar/foo')); + $this->assertSame('baz', $this->session->get('bar/foobar')); + } +} diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 7bf03b6e874d4..95e9805673a66 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -25,9 +25,12 @@ }, "require-dev": { "symfony/routing": "~3.4|~4.0", - "symfony/security-csrf": "~3.4|~4.0", + "symfony/security-csrf": "^3.4.11|^4.0.11", "psr/log": "~1.0" }, + "conflict": { + "symfony/security-csrf": "<3.4.11|~4.0,<4.0.11|~4.1,<=4.1.0-beta2" + }, "suggest": { "symfony/security-csrf": "For using tokens to protect authentication/logout attempts", "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs" diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 9c97ff9893fa6..c6a41332a721c 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -376,6 +376,7 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str { if ( !isset($context[static::ENABLE_MAX_DEPTH]) || + !$context[static::ENABLE_MAX_DEPTH] || !isset($attributesMetadata[$attribute]) || null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() ) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php index 2ba258ecb7271..bc9aa60bd3a08 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php @@ -32,21 +32,37 @@ public function normalize($object, $format = null, array $context = array()) $violations = array(); $messages = array(); foreach ($object as $violation) { - $violations[] = array( - 'propertyPath' => $violation->getPropertyPath(), - 'message' => $violation->getMessage(), - 'code' => $violation->getCode(), - ); $propertyPath = $violation->getPropertyPath(); + + $violationEntry = array( + 'propertyPath' => $propertyPath, + 'title' => $violation->getMessage(), + ); + if (null !== $code = $violation->getCode()) { + $violationEntry['type'] = sprintf('urn:uuid:%s', $code); + } + + $violations[] = $violationEntry; + $prefix = $propertyPath ? sprintf('%s: ', $propertyPath) : ''; $messages[] = $prefix.$violation->getMessage(); } - return array( - 'title' => isset($context['title']) ? $context['title'] : 'An error occurred', - 'detail' => $messages ? implode("\n", $messages) : '', - 'violations' => $violations, + $result = array( + 'type' => $context['type'] ?? 'https://symfony.com/errors/validation', + 'title' => $context['title'] ?? 'Validation Failed', ); + if (isset($context['status'])) { + $result['status'] = $context['status']; + } + if ($messages) { + $result['detail'] = implode("\n", $messages); + } + if (isset($context['instance'])) { + $result['instance'] = $context['instance']; + } + + return $result + array('violations' => $violations); } /** diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php index 5c9c55028ff2f..9e8aec51477ba 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ConstraintViolationListNormalizerTest.php @@ -43,19 +43,20 @@ public function testNormalize() )); $expected = array( - 'title' => 'An error occurred', + 'type' => 'https://symfony.com/errors/validation', + 'title' => 'Validation Failed', 'detail' => 'd: a 4: 1', 'violations' => array( array( 'propertyPath' => 'd', - 'message' => 'a', - 'code' => 'f', + 'title' => 'a', + 'type' => 'urn:uuid:f', ), array( 'propertyPath' => '4', - 'message' => '1', - 'code' => '6', + 'title' => '1', + 'type' => 'urn:uuid:6', ), ), ); diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf index 834db4015e8e4..6f5fd98ca192e 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). To ni veljavna identifikacijska koda podjetja (BIC). + + Error + Napaka +