Merge branch '2.8' into 3.4

* 2.8:
  [Security] Fix logout
  #27250 limiting GET_LOCK key up to 64 char due to changes in MySQL 5.7.5 and later
  [Profiler] Remove propel & event_listener_loading category identifiers
  [Filesystem] Fix usages of error_get_last()
  [Debug] Fix populating error_get_last() for handled silent errors
  Suppress warnings when open_basedir is non-empty
This commit is contained in:
Nicolas Grekas 2018-05-16 10:49:21 +02:00
commit 86a9c73026
27 changed files with 271 additions and 91 deletions

View File

@ -280,14 +280,15 @@ class SecurityExtension extends Extension
$configId = 'security.firewall.map.config.'.$name; $configId = 'security.firewall.map.config.'.$name;
list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
$contextId = 'security.firewall.map.context.'.$name; $contextId = 'security.firewall.map.context.'.$name;
$context = $container->setDefinition($contextId, new ChildDefinition('security.firewall.context')); $context = $container->setDefinition($contextId, new ChildDefinition('security.firewall.context'));
$context $context
->replaceArgument(0, new IteratorArgument($listeners)) ->replaceArgument(0, new IteratorArgument($listeners))
->replaceArgument(1, $exceptionListener) ->replaceArgument(1, $exceptionListener)
->replaceArgument(2, new Reference($configId)) ->replaceArgument(2, $logoutListener)
->replaceArgument(3, new Reference($configId))
; ;
$contextRefs[$contextId] = new Reference($contextId); $contextRefs[$contextId] = new Reference($contextId);
@ -333,7 +334,7 @@ class SecurityExtension extends Extension
// Security disabled? // Security disabled?
if (false === $firewall['security']) { if (false === $firewall['security']) {
return array($matcher, array(), null); return array($matcher, array(), null, null);
} }
$config->replaceArgument(4, $firewall['stateless']); $config->replaceArgument(4, $firewall['stateless']);
@ -381,16 +382,15 @@ class SecurityExtension extends Extension
$config->replaceArgument(6, $contextKey); $config->replaceArgument(6, $contextKey);
// Logout listener // Logout listener
$logoutListenerId = null;
if (isset($firewall['logout'])) { if (isset($firewall['logout'])) {
$listenerKeys[] = 'logout'; $logoutListenerId = 'security.logout_listener.'.$id;
$listenerId = 'security.logout_listener.'.$id; $logoutListener = $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener'));
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.logout_listener')); $logoutListener->replaceArgument(3, array(
$listener->replaceArgument(3, array(
'csrf_parameter' => $firewall['logout']['csrf_parameter'], 'csrf_parameter' => $firewall['logout']['csrf_parameter'],
'csrf_token_id' => $firewall['logout']['csrf_token_id'], 'csrf_token_id' => $firewall['logout']['csrf_token_id'],
'logout_path' => $firewall['logout']['path'], 'logout_path' => $firewall['logout']['path'],
)); ));
$listeners[] = new Reference($listenerId);
// add logout success handler // add logout success handler
if (isset($firewall['logout']['success_handler'])) { if (isset($firewall['logout']['success_handler'])) {
@ -400,16 +400,16 @@ class SecurityExtension extends Extension
$logoutSuccessHandler = $container->setDefinition($logoutSuccessHandlerId, new ChildDefinition('security.logout.success_handler')); $logoutSuccessHandler = $container->setDefinition($logoutSuccessHandlerId, new ChildDefinition('security.logout.success_handler'));
$logoutSuccessHandler->replaceArgument(1, $firewall['logout']['target']); $logoutSuccessHandler->replaceArgument(1, $firewall['logout']['target']);
} }
$listener->replaceArgument(2, new Reference($logoutSuccessHandlerId)); $logoutListener->replaceArgument(2, new Reference($logoutSuccessHandlerId));
// add CSRF provider // add CSRF provider
if (isset($firewall['logout']['csrf_token_generator'])) { if (isset($firewall['logout']['csrf_token_generator'])) {
$listener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator']));
} }
// add session logout handler // add session logout handler
if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) { if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) {
$listener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session'))); $logoutListener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session')));
} }
// add cookie logout handler // add cookie logout handler
@ -418,12 +418,12 @@ class SecurityExtension extends Extension
$cookieHandler = $container->setDefinition($cookieHandlerId, new ChildDefinition('security.logout.handler.cookie_clearing')); $cookieHandler = $container->setDefinition($cookieHandlerId, new ChildDefinition('security.logout.handler.cookie_clearing'));
$cookieHandler->addArgument($firewall['logout']['delete_cookies']); $cookieHandler->addArgument($firewall['logout']['delete_cookies']);
$listener->addMethodCall('addHandler', array(new Reference($cookieHandlerId))); $logoutListener->addMethodCall('addHandler', array(new Reference($cookieHandlerId)));
} }
// add custom handlers // add custom handlers
foreach ($firewall['logout']['handlers'] as $handlerId) { foreach ($firewall['logout']['handlers'] as $handlerId) {
$listener->addMethodCall('addHandler', array(new Reference($handlerId))); $logoutListener->addMethodCall('addHandler', array(new Reference($handlerId)));
} }
// register with LogoutUrlGenerator // register with LogoutUrlGenerator
@ -483,7 +483,7 @@ class SecurityExtension extends Extension
$config->replaceArgument(10, $listenerKeys); $config->replaceArgument(10, $listenerKeys);
$config->replaceArgument(11, isset($firewall['switch_user']) ? $firewall['switch_user'] : null); $config->replaceArgument(11, isset($firewall['switch_user']) ? $firewall['switch_user'] : null);
return array($matcher, $listeners, $exceptionListener); return array($matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null);
} }
private function createContextListener($container, $contextKey, $logoutUserOnChange) private function createContextListener($container, $contextKey, $logoutUserOnChange)

View File

@ -138,6 +138,7 @@
<service id="security.firewall.context" class="Symfony\Bundle\SecurityBundle\Security\FirewallContext" abstract="true"> <service id="security.firewall.context" class="Symfony\Bundle\SecurityBundle\Security\FirewallContext" abstract="true">
<argument type="collection" /> <argument type="collection" />
<argument type="service" id="security.exception_listener" /> <argument type="service" id="security.exception_listener" />
<argument /> <!-- LogoutListener -->
<argument /> <!-- FirewallConfig --> <argument /> <!-- FirewallConfig -->
</service> </service>

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\SecurityBundle\Security; namespace Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
/** /**
* This is a wrapper around the actual firewall configuration which allows us * This is a wrapper around the actual firewall configuration which allows us
@ -23,18 +24,25 @@ class FirewallContext
{ {
private $listeners; private $listeners;
private $exceptionListener; private $exceptionListener;
private $logoutListener;
private $config; private $config;
/** /**
* @param \Traversable|array $listeners * @param \Traversable|array $listeners
* @param ExceptionListener|null $exceptionListener * @param LogoutListener|null $logoutListener
* @param FirewallConfig|null $firewallConfig
*/ */
public function __construct($listeners, ExceptionListener $exceptionListener = null, FirewallConfig $config = null) public function __construct($listeners, ExceptionListener $exceptionListener = null, $logoutListener = null, FirewallConfig $config = null)
{ {
$this->listeners = $listeners; $this->listeners = $listeners;
$this->exceptionListener = $exceptionListener; $this->exceptionListener = $exceptionListener;
if ($logoutListener instanceof FirewallConfig) {
$this->config = $logoutListener;
} elseif (null === $logoutListener || $logoutListener instanceof LogoutListener) {
$this->logoutListener = $logoutListener;
$this->config = $config; $this->config = $config;
} else {
throw new \InvalidArgumentException(sprintf('Argument 3 passed to %s() must be instance of %s or null, %s given.', __METHOD__, LogoutListener::class, is_object($logoutListener) ? get_class($logoutListener) : gettype($logoutListener)));
}
} }
public function getConfig() public function getConfig()
@ -49,7 +57,7 @@ class FirewallContext
{ {
@trigger_error(sprintf('Method %s() is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s::getListeners/getExceptionListener() instead.', __METHOD__, __CLASS__), E_USER_DEPRECATED); @trigger_error(sprintf('Method %s() is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s::getListeners/getExceptionListener() instead.', __METHOD__, __CLASS__), E_USER_DEPRECATED);
return array($this->getListeners(), $this->getExceptionListener()); return array($this->getListeners(), $this->getExceptionListener(), $this->getLogoutListener());
} }
/** /**
@ -64,4 +72,9 @@ class FirewallContext
{ {
return $this->exceptionListener; return $this->exceptionListener;
} }
public function getLogoutListener()
{
return $this->logoutListener;
}
} }

View File

@ -121,10 +121,10 @@ class _FirewallMap
$context = $this->getFirewallContext($request); $context = $this->getFirewallContext($request);
if (null === $context) { if (null === $context) {
return array(array(), null); return array(array(), null, null);
} }
return array($context->getListeners(), $context->getExceptionListener()); return array($context->getListeners(), $context->getExceptionListener(), $context->getLogoutListener());
} }
/** /**

View File

@ -85,7 +85,7 @@ abstract class CompleteConfigurationTest extends TestCase
$arguments = $contextDef->getArguments(); $arguments = $contextDef->getArguments();
$listeners[] = array_map('strval', $arguments['index_0']->getValues()); $listeners[] = array_map('strval', $arguments['index_0']->getValues());
$configDef = $container->getDefinition((string) $arguments['index_2']); $configDef = $container->getDefinition((string) $arguments['index_3']);
$configs[] = array_values($configDef->getArguments()); $configs[] = array_values($configDef->getArguments());
} }
@ -113,7 +113,6 @@ abstract class CompleteConfigurationTest extends TestCase
null, null,
null, null,
array( array(
'logout',
'switch_user', 'switch_user',
'x509', 'x509',
'remote_user', 'remote_user',
@ -185,7 +184,6 @@ abstract class CompleteConfigurationTest extends TestCase
array(), array(),
array( array(
'security.channel_listener', 'security.channel_listener',
'security.logout_listener.secure',
'security.authentication.listener.x509.secure', 'security.authentication.listener.x509.secure',
'security.authentication.listener.remote_user.secure', 'security.authentication.listener.remote_user.secure',
'security.authentication.listener.form.secure', 'security.authentication.listener.form.secure',
@ -235,7 +233,7 @@ abstract class CompleteConfigurationTest extends TestCase
$arguments = $contextDef->getArguments(); $arguments = $contextDef->getArguments();
$listeners[] = array_map('strval', $arguments['index_0']->getValues()); $listeners[] = array_map('strval', $arguments['index_0']->getValues());
$configDef = $container->getDefinition((string) $arguments['index_2']); $configDef = $container->getDefinition((string) $arguments['index_3']);
$configs[] = array_values($configDef->getArguments()); $configs[] = array_values($configDef->getArguments());
} }
@ -263,7 +261,6 @@ abstract class CompleteConfigurationTest extends TestCase
null, null,
null, null,
array( array(
'logout',
'switch_user', 'switch_user',
'x509', 'x509',
'remote_user', 'remote_user',
@ -319,7 +316,6 @@ abstract class CompleteConfigurationTest extends TestCase
array(), array(),
array( array(
'security.channel_listener', 'security.channel_listener',
'security.logout_listener.secure',
'security.authentication.listener.x509.secure', 'security.authentication.listener.x509.secure',
'security.authentication.listener.remote_user.secure', 'security.authentication.listener.remote_user.secure',
'security.authentication.listener.form.secure', 'security.authentication.listener.form.secure',

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
class LogoutTest extends WebTestCase
{
public function testSessionLessRememberMeLogout()
{
$client = $this->createClient(array('test_case' => 'RememberMeLogout', 'root_config' => 'config.yml'));
$client->request('POST', '/login', array(
'_username' => 'johannes',
'_password' => 'test',
));
$cookieJar = $client->getCookieJar();
$cookieJar->expire(session_name());
$this->assertNotNull($cookieJar->get('REMEMBERME'));
$client->request('GET', '/logout');
$this->assertNull($cookieJar->get('REMEMBERME'));
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
return array(
new FrameworkBundle(),
new SecurityBundle(),
);

View File

@ -0,0 +1,25 @@
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: key
logout: ~
anonymous: ~
stateless: true

View File

@ -0,0 +1,5 @@
login:
path: /login
logout:
path: /logout

View File

@ -16,6 +16,7 @@ use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
use Symfony\Bundle\SecurityBundle\Security\FirewallContext; use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\ListenerInterface; use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
class FirewallContextTest extends TestCase class FirewallContextTest extends TestCase
{ {
@ -23,6 +24,7 @@ class FirewallContextTest extends TestCase
{ {
$config = new FirewallConfig('main', 'user_checker', 'request_matcher'); $config = new FirewallConfig('main', 'user_checker', 'request_matcher');
$exceptionListener = $this->getExceptionListenerMock(); $exceptionListener = $this->getExceptionListenerMock();
$logoutListener = $this->getLogoutListenerMock();
$listeners = array( $listeners = array(
$this $this
->getMockBuilder(ListenerInterface::class) ->getMockBuilder(ListenerInterface::class)
@ -30,10 +32,11 @@ class FirewallContextTest extends TestCase
->getMock(), ->getMock(),
); );
$context = new FirewallContext($listeners, $exceptionListener, $config); $context = new FirewallContext($listeners, $exceptionListener, $logoutListener, $config);
$this->assertEquals($listeners, $context->getListeners()); $this->assertEquals($listeners, $context->getListeners());
$this->assertEquals($exceptionListener, $context->getExceptionListener()); $this->assertEquals($exceptionListener, $context->getExceptionListener());
$this->assertEquals($logoutListener, $context->getLogoutListener());
$this->assertEquals($config, $context->getConfig()); $this->assertEquals($config, $context->getConfig());
} }
@ -43,10 +46,12 @@ class FirewallContextTest extends TestCase
*/ */
public function testGetContext() public function testGetContext()
{ {
$context = (new FirewallContext($listeners = array(), $exceptionListener = $this->getExceptionListenerMock(), new FirewallConfig('main', 'request_matcher', 'user_checker'))) $exceptionListener = $this->getExceptionListenerMock();
$logoutListener = $this->getLogoutListenerMock();
$context = (new FirewallContext($listeners = array(), $exceptionListener, $logoutListener, new FirewallConfig('main', 'request_matcher', 'user_checker')))
->getContext(); ->getContext();
$this->assertEquals(array($listeners, $exceptionListener), $context); $this->assertEquals(array($listeners, $exceptionListener, $logoutListener), $context);
} }
private function getExceptionListenerMock() private function getExceptionListenerMock()
@ -56,4 +61,12 @@ class FirewallContextTest extends TestCase
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
} }
private function getLogoutListenerMock()
{
return $this
->getMockBuilder(LogoutListener::class)
->disableOriginalConstructor()
->getMock();
}
} }

View File

@ -21,6 +21,7 @@ use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\ListenerInterface; use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
class FirewallMapTest extends TestCase class FirewallMapTest extends TestCase
{ {
@ -36,7 +37,7 @@ class FirewallMapTest extends TestCase
$firewallMap = new FirewallMap($container, $map); $firewallMap = new FirewallMap($container, $map);
$this->assertEquals(array(array(), null), $firewallMap->getListeners($request)); $this->assertEquals(array(array(), null, null), $firewallMap->getListeners($request));
$this->assertNull($firewallMap->getFirewallConfig($request)); $this->assertNull($firewallMap->getFirewallConfig($request));
$this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT)); $this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT));
} }
@ -52,7 +53,7 @@ class FirewallMapTest extends TestCase
$firewallMap = new FirewallMap($container, $map); $firewallMap = new FirewallMap($container, $map);
$this->assertEquals(array(array(), null), $firewallMap->getListeners($request)); $this->assertEquals(array(array(), null, null), $firewallMap->getListeners($request));
$this->assertNull($firewallMap->getFirewallConfig($request)); $this->assertNull($firewallMap->getFirewallConfig($request));
$this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT)); $this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT));
} }
@ -72,6 +73,9 @@ class FirewallMapTest extends TestCase
$exceptionListener = $this->getMockBuilder(ExceptionListener::class)->disableOriginalConstructor()->getMock(); $exceptionListener = $this->getMockBuilder(ExceptionListener::class)->disableOriginalConstructor()->getMock();
$firewallContext->expects($this->once())->method('getExceptionListener')->willReturn($exceptionListener); $firewallContext->expects($this->once())->method('getExceptionListener')->willReturn($exceptionListener);
$logoutListener = $this->getMockBuilder(LogoutListener::class)->disableOriginalConstructor()->getMock();
$firewallContext->expects($this->once())->method('getLogoutListener')->willReturn($logoutListener);
$matcher = $this->getMockBuilder(RequestMatcherInterface::class)->getMock(); $matcher = $this->getMockBuilder(RequestMatcherInterface::class)->getMock();
$matcher->expects($this->once()) $matcher->expects($this->once())
->method('matches') ->method('matches')
@ -83,7 +87,7 @@ class FirewallMapTest extends TestCase
$firewallMap = new FirewallMap($container, array('security.firewall.map.context.foo' => $matcher)); $firewallMap = new FirewallMap($container, array('security.firewall.map.context.foo' => $matcher));
$this->assertEquals(array(array($listener), $exceptionListener), $firewallMap->getListeners($request)); $this->assertEquals(array(array($listener), $exceptionListener, $logoutListener), $firewallMap->getListeners($request));
$this->assertEquals($firewallConfig, $firewallMap->getFirewallConfig($request)); $this->assertEquals($firewallConfig, $firewallMap->getFirewallConfig($request));
$this->assertEquals('security.firewall.map.context.foo', $request->attributes->get(self::ATTRIBUTE_FIREWALL_CONTEXT)); $this->assertEquals('security.firewall.map.context.foo', $request->attributes->get(self::ATTRIBUTE_FIREWALL_CONTEXT));
} }

View File

@ -18,7 +18,7 @@
"require": { "require": {
"php": "^5.5.9|>=7.0.8", "php": "^5.5.9|>=7.0.8",
"ext-xml": "*", "ext-xml": "*",
"symfony/security": "~3.4|~4.0", "symfony/security": "~3.4.10|^4.0.10",
"symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/dependency-injection": "^3.4.3|^4.0.3",
"symfony/http-kernel": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0",
"symfony/polyfill-php70": "~1.0" "symfony/polyfill-php70": "~1.0"

View File

@ -7,10 +7,8 @@
'default': '#999', 'default': '#999',
'section': '#444', 'section': '#444',
'event_listener': '#00B8F5', 'event_listener': '#00B8F5',
'event_listener_loading': '#00B8F5',
'template': '#66CC00', 'template': '#66CC00',
'doctrine': '#FF6633', 'doctrine': '#FF6633',
'propel': '#FF6633',
} %} } %}
{% endif %} {% endif %}

View File

@ -218,12 +218,11 @@ class Command
if (null !== $this->processTitle) { if (null !== $this->processTitle) {
if (function_exists('cli_set_process_title')) { if (function_exists('cli_set_process_title')) {
if (false === @cli_set_process_title($this->processTitle)) { if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === PHP_OS) { if ('Darwin' === PHP_OS) {
$output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>'); $output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>');
} else { } else {
$error = error_get_last(); cli_set_process_title($this->processTitle);
trigger_error($error['message'], E_USER_WARNING);
} }
} }
} elseif (function_exists('setproctitle')) { } elseif (function_exists('setproctitle')) {

View File

@ -383,14 +383,16 @@ class ErrorHandler
public function handleError($type, $message, $file, $line) public function handleError($type, $message, $file, $line)
{ {
// Level is the current error reporting level to manage silent error. // Level is the current error reporting level to manage silent error.
$level = error_reporting();
$silenced = 0 === ($level & $type);
// Strong errors are not authorized to be silenced. // Strong errors are not authorized to be silenced.
$level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
$log = $this->loggedErrors & $type; $log = $this->loggedErrors & $type;
$throw = $this->thrownErrors & $type & $level; $throw = $this->thrownErrors & $type & $level;
$type &= $level | $this->screamedErrors; $type &= $level | $this->screamedErrors;
if (!$type || (!$log && !$throw)) { if (!$type || (!$log && !$throw)) {
return $type && $log; return !$silenced && $type && $log;
} }
$scope = $this->scopedErrors & $type; $scope = $this->scopedErrors & $type;
@ -524,7 +526,7 @@ class ErrorHandler
} }
} }
return $type && $log; return !$silenced && $type && $log;
} }
/** /**

View File

@ -65,6 +65,30 @@ class ErrorHandlerTest extends TestCase
} }
} }
public function testErrorGetLast()
{
$handler = ErrorHandler::register();
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$handler->setDefaultLogger($logger);
$handler->screamAt(E_ALL);
try {
@trigger_error('Hello', E_USER_WARNING);
$expected = array(
'type' => E_USER_WARNING,
'message' => 'Hello',
'file' => __FILE__,
'line' => __LINE__ - 5,
);
$this->assertSame($expected, error_get_last());
} catch (\Exception $e) {
restore_error_handler();
restore_exception_handler();
throw $e;
}
}
public function testNotice() public function testNotice()
{ {
ErrorHandler::register(); ErrorHandler::register();

View File

@ -21,6 +21,8 @@ use Symfony\Component\Filesystem\Exception\FileNotFoundException;
*/ */
class Filesystem class Filesystem
{ {
private static $lastError;
/** /**
* Copies a file. * Copies a file.
* *
@ -95,12 +97,11 @@ class Filesystem
continue; continue;
} }
if (true !== @mkdir($dir, $mode, true)) { if (!self::box('mkdir', $dir, $mode, true)) {
$error = error_get_last();
if (!is_dir($dir)) { if (!is_dir($dir)) {
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
if ($error) { if (self::$lastError) {
throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message']), 0, null, $dir); throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir);
} }
throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir); throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir);
} }
@ -169,20 +170,17 @@ class Filesystem
foreach ($files as $file) { foreach ($files as $file) {
if (is_link($file)) { if (is_link($file)) {
// See https://bugs.php.net/52176 // See https://bugs.php.net/52176
if (!@(unlink($file) || '\\' !== DIRECTORY_SEPARATOR || rmdir($file)) && file_exists($file)) { if (!(self::box('unlink', $file) || '\\' !== DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
$error = error_get_last(); throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError));
throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message']));
} }
} elseif (is_dir($file)) { } elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
if (!@rmdir($file) && file_exists($file)) { if (!self::box('rmdir', $file) && file_exists($file)) {
$error = error_get_last(); throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError));
throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, $error['message']));
} }
} elseif (!@unlink($file) && file_exists($file)) { } elseif (!self::box('unlink', $file) && file_exists($file)) {
$error = error_get_last(); throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError));
throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message']));
} }
} }
} }
@ -336,16 +334,14 @@ class Filesystem
$this->mkdir(dirname($targetDir)); $this->mkdir(dirname($targetDir));
$ok = false;
if (is_link($targetDir)) { if (is_link($targetDir)) {
if (readlink($targetDir) != $originDir) { if (readlink($targetDir) === $originDir) {
$this->remove($targetDir); return;
} else {
$ok = true;
} }
$this->remove($targetDir);
} }
if (!$ok && true !== @symlink($originDir, $targetDir)) { if (!self::box('symlink', $originDir, $targetDir)) {
$this->linkException($originDir, $targetDir, 'symbolic'); $this->linkException($originDir, $targetDir, 'symbolic');
} }
} }
@ -377,7 +373,7 @@ class Filesystem
$this->remove($targetFile); $this->remove($targetFile);
} }
if (true !== @link($originFile, $targetFile)) { if (!self::box('link', $originFile, $targetFile)) {
$this->linkException($originFile, $targetFile, 'hard'); $this->linkException($originFile, $targetFile, 'hard');
} }
} }
@ -390,9 +386,8 @@ class Filesystem
*/ */
private function linkException($origin, $target, $linkType) private function linkException($origin, $target, $linkType)
{ {
$report = error_get_last(); if (self::$lastError) {
if (is_array($report)) { if ('\\' === DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) {
throw new IOException(sprintf('Unable to create %s link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target); throw new IOException(sprintf('Unable to create %s link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
} }
} }
@ -747,4 +742,29 @@ class Filesystem
return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]); return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]);
} }
private static function box($func)
{
self::$lastError = null;
\set_error_handler(__CLASS__.'::handleError');
try {
$result = \call_user_func_array($func, \array_slice(\func_get_args(), 1));
\restore_error_handler();
return $result;
} catch (\Throwable $e) {
} catch (\Exception $e) {
}
\restore_error_handler();
throw $e;
}
/**
* @internal
*/
public static function handleError($type, $msg)
{
self::$lastError = $msg;
}
} }

View File

@ -66,12 +66,11 @@ class SplFileInfo extends \SplFileInfo
*/ */
public function getContents() public function getContents()
{ {
$level = error_reporting(0); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$content = file_get_contents($this->getPathname()); $content = file_get_contents($this->getPathname());
error_reporting($level); restore_error_handler();
if (false === $content) { if (false === $content) {
$error = error_get_last(); throw new \RuntimeException($error);
throw new \RuntimeException($error['message']);
} }
return $content; return $content;

View File

@ -93,9 +93,11 @@ class File extends \SplFileInfo
{ {
$target = $this->getTargetFile($directory, $name); $target = $this->getTargetFile($directory, $name);
if (!@rename($this->getPathname(), $target)) { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$error = error_get_last(); $renamed = rename($this->getPathname(), $target);
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); restore_error_handler();
if (!$renamed) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
} }
@chmod($target, 0666 & ~umask()); @chmod($target, 0666 & ~umask());

View File

@ -192,9 +192,11 @@ class UploadedFile extends File
$target = $this->getTargetFile($directory, $name); $target = $this->getTargetFile($directory, $name);
if (!@move_uploaded_file($this->getPathname(), $target)) { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$error = error_get_last(); $moved = move_uploaded_file($this->getPathname(), $target);
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); restore_error_handler();
if (!$moved) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
} }
@chmod($target, 0666 & ~umask()); @chmod($target, 0666 & ~umask());

View File

@ -676,14 +676,16 @@ class PdoSessionHandler extends AbstractSessionHandler
{ {
switch ($this->driver) { switch ($this->driver) {
case 'mysql': case 'mysql':
// MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced.
$lockId = \substr($sessionId, 0, 64);
// should we handle the return value? 0 on timeout, null on error // should we handle the return value? 0 on timeout, null on error
// we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
$stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
$stmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR);
$stmt->execute(); $stmt->execute();
$releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)');
$releaseStmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR);
return $releaseStmt; return $releaseStmt;
case 'pgsql': case 'pgsql':

View File

@ -77,7 +77,7 @@ class ExecutableFinder
} }
foreach ($suffixes as $suffix) { foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) { foreach ($dirs as $dir) {
if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) { if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file; return $file;
} }
} }

View File

@ -49,7 +49,7 @@ class PhpExecutableFinder
} }
if ($php = getenv('PHP_PATH')) { if ($php = getenv('PHP_PATH')) {
if (!is_executable($php)) { if (!@is_executable($php)) {
return false; return false;
} }
@ -57,12 +57,12 @@ class PhpExecutableFinder
} }
if ($php = getenv('PHP_PEAR_PHP_BIN')) { if ($php = getenv('PHP_PEAR_PHP_BIN')) {
if (is_executable($php)) { if (@is_executable($php)) {
return $php; return $php;
} }
} }
if (is_executable($php = PHP_BINDIR.('\\' === DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) { if (@is_executable($php = PHP_BINDIR.('\\' === DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
return $php; return $php;
} }

View File

@ -25,6 +25,7 @@ abstract class AbstractPipes implements PipesInterface
private $inputBuffer = ''; private $inputBuffer = '';
private $input; private $input;
private $blocked = true; private $blocked = true;
private $lastError;
/** /**
* @param resource|string|int|float|bool|\Iterator|null $input * @param resource|string|int|float|bool|\Iterator|null $input
@ -58,10 +59,11 @@ abstract class AbstractPipes implements PipesInterface
*/ */
protected function hasSystemCallBeenInterrupted() protected function hasSystemCallBeenInterrupted()
{ {
$lastError = error_get_last(); $lastError = $this->lastError;
$this->lastError = null;
// stream_select returns false when the `select` system call is interrupted by an incoming signal // stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
} }
/** /**
@ -165,4 +167,12 @@ abstract class AbstractPipes implements PipesInterface
return array($this->pipes[0]); return array($this->pipes[0]);
} }
} }
/**
* @internal
*/
public function handleError($type, $msg)
{
$this->lastError = $msg;
}
} }

View File

@ -99,7 +99,9 @@ class UnixPipes extends AbstractPipes
unset($r[0]); unset($r[0]);
// let's have a look if something changed in streams // let's have a look if something changed in streams
if (($r || $w) && false === @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { set_error_handler(array($this, 'handleError'));
if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
restore_error_handler();
// if a system call has been interrupted, forget about it, let's try again // if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes // otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) { if (!$this->hasSystemCallBeenInterrupted()) {
@ -108,6 +110,7 @@ class UnixPipes extends AbstractPipes
return $read; return $read;
} }
restore_error_handler();
foreach ($r as $pipe) { foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and // prior PHP 5.4 the array passed to stream_select is modified and

View File

@ -47,13 +47,22 @@ class Firewall implements EventSubscriberInterface
} }
// register listeners for this firewall // register listeners for this firewall
list($listeners, $exceptionListener) = $this->map->getListeners($event->getRequest()); $listeners = $this->map->getListeners($event->getRequest());
$authenticationListeners = $listeners[0];
$exceptionListener = $listeners[1];
$logoutListener = isset($listeners[2]) ? $listeners[2] : null;
if (null !== $exceptionListener) { if (null !== $exceptionListener) {
$this->exceptionListeners[$event->getRequest()] = $exceptionListener; $this->exceptionListeners[$event->getRequest()] = $exceptionListener;
$exceptionListener->register($this->dispatcher); $exceptionListener->register($this->dispatcher);
} }
return $this->handleRequest($event, $listeners); $this->handleRequest($event, $authenticationListeners);
if (null !== $logoutListener) {
$logoutListener->handle($event);
}
} }
public function onKernelFinishRequest(FinishRequestEvent $event) public function onKernelFinishRequest(FinishRequestEvent $event)

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http;
use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
/** /**
* FirewallMap allows configuration of different firewalls for specific parts * FirewallMap allows configuration of different firewalls for specific parts
@ -25,9 +26,9 @@ class FirewallMap implements FirewallMapInterface
{ {
private $map = array(); private $map = array();
public function add(RequestMatcherInterface $requestMatcher = null, array $listeners = array(), ExceptionListener $exceptionListener = null) public function add(RequestMatcherInterface $requestMatcher = null, array $listeners = array(), ExceptionListener $exceptionListener = null, LogoutListener $logoutListener = null)
{ {
$this->map[] = array($requestMatcher, $listeners, $exceptionListener); $this->map[] = array($requestMatcher, $listeners, $exceptionListener, $logoutListener);
} }
/** /**
@ -37,10 +38,10 @@ class FirewallMap implements FirewallMapInterface
{ {
foreach ($this->map as $elements) { foreach ($this->map as $elements) {
if (null === $elements[0] || $elements[0]->matches($request)) { if (null === $elements[0] || $elements[0]->matches($request)) {
return array($elements[1], $elements[2]); return array($elements[1], $elements[2], $elements[3]);
} }
} }
return array(array(), null); return array(array(), null, null);
} }
} }