diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
index 3ffe2876e8..9c526f88da 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
@@ -222,13 +222,14 @@ class SecurityExtension extends Extension
$mapDef = $container->getDefinition('security.firewall.map');
$map = $authenticationProviders = array();
foreach ($firewalls as $name => $firewall) {
- list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds);
+ list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds);
$contextId = 'security.firewall.map.context.'.$name;
$context = $container->setDefinition($contextId, new DefinitionDecorator('security.firewall.context'));
$context
->replaceArgument(0, $listeners)
->replaceArgument(1, $exceptionListener)
+ ->replaceArgument(2, $logoutListener)
;
$map[$contextId] = $matcher;
}
@@ -259,7 +260,7 @@ class SecurityExtension extends Extension
// Security disabled?
if (false === $firewall['security']) {
- return array($matcher, array(), null);
+ return array($matcher, array(), null, null);
}
// Provider id (take the first registered provider if none defined)
@@ -286,15 +287,15 @@ class SecurityExtension extends Extension
}
// Logout listener
+ $logoutListenerId = null;
if (isset($firewall['logout'])) {
- $listenerId = 'security.logout_listener.'.$id;
- $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.logout_listener'));
- $listener->replaceArgument(3, array(
+ $logoutListenerId = 'security.logout_listener.'.$id;
+ $logoutListener = $container->setDefinition($logoutListenerId, new DefinitionDecorator('security.logout_listener'));
+ $logoutListener->replaceArgument(3, array(
'csrf_parameter' => $firewall['logout']['csrf_parameter'],
'intention' => $firewall['logout']['csrf_token_id'],
'logout_path' => $firewall['logout']['path'],
));
- $listeners[] = new Reference($listenerId);
// add logout success handler
if (isset($firewall['logout']['success_handler'])) {
@@ -304,16 +305,16 @@ class SecurityExtension extends Extension
$logoutSuccessHandler = $container->setDefinition($logoutSuccessHandlerId, new DefinitionDecorator('security.logout.success_handler'));
$logoutSuccessHandler->replaceArgument(1, $firewall['logout']['target']);
}
- $listener->replaceArgument(2, new Reference($logoutSuccessHandlerId));
+ $logoutListener->replaceArgument(2, new Reference($logoutSuccessHandlerId));
// add CSRF provider
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
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
@@ -322,12 +323,12 @@ class SecurityExtension extends Extension
$cookieHandler = $container->setDefinition($cookieHandlerId, new DefinitionDecorator('security.logout.handler.cookie_clearing'));
$cookieHandler->addArgument($firewall['logout']['delete_cookies']);
- $listener->addMethodCall('addHandler', array(new Reference($cookieHandlerId)));
+ $logoutListener->addMethodCall('addHandler', array(new Reference($cookieHandlerId)));
}
// add custom handlers
foreach ($firewall['logout']['handlers'] as $handlerId) {
- $listener->addMethodCall('addHandler', array(new Reference($handlerId)));
+ $logoutListener->addMethodCall('addHandler', array(new Reference($handlerId)));
}
// register with LogoutUrlGenerator
@@ -362,7 +363,7 @@ class SecurityExtension extends Extension
// Exception listener
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
- return array($matcher, $listeners, $exceptionListener);
+ return array($matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null);
}
private function createContextListener($container, $contextKey)
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
index b7c1407c1c..b044ccba98 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
@@ -150,6 +150,7 @@
+
diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php
index 13d096d97e..e9f8fe66d6 100644
--- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php
+++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php
@@ -12,6 +12,7 @@
namespace Symfony\Bundle\SecurityBundle\Security;
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
@@ -23,15 +24,17 @@ class FirewallContext
{
private $listeners;
private $exceptionListener;
+ private $logoutListener;
- public function __construct(array $listeners, ExceptionListener $exceptionListener = null)
+ public function __construct(array $listeners, ExceptionListener $exceptionListener = null, LogoutListener $logoutListener = null)
{
$this->listeners = $listeners;
$this->exceptionListener = $exceptionListener;
+ $this->logoutListener = $logoutListener;
}
public function getContext()
{
- return array($this->listeners, $this->exceptionListener);
+ return array($this->listeners, $this->exceptionListener, $this->logoutListener);
}
}
diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php
index dc87681c37..d45d7b87f0 100644
--- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php
+++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php
@@ -41,6 +41,6 @@ class FirewallMap implements FirewallMapInterface
}
}
- return array(array(), null);
+ return array(array(), null, null);
}
}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
index 855bed9b68..371baa704a 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
@@ -76,7 +76,6 @@ abstract class CompleteConfigurationTest extends TestCase
array(),
array(
'security.channel_listener',
- 'security.logout_listener.secure',
'security.authentication.listener.x509.secure',
'security.authentication.listener.remote_user.secure',
'security.authentication.listener.form.secure',
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php
new file mode 100644
index 0000000000..7eeb7c2117
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php
@@ -0,0 +1,34 @@
+
+ *
+ * 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'));
+ }
+}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php
new file mode 100644
index 0000000000..d90f774abd
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/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/RememberMeLogout/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml
new file mode 100644
index 0000000000..48fd4ed6cc
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml
@@ -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
+ key: key
+ logout: ~
+ anonymous: ~
+ stateless: true
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/routing.yml
new file mode 100644
index 0000000000..1dddfca2f8
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/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 e765b16bef..90edcf6e38 100644
--- a/src/Symfony/Bundle/SecurityBundle/composer.json
+++ b/src/Symfony/Bundle/SecurityBundle/composer.json
@@ -18,7 +18,7 @@
"require": {
"php": ">=5.3.9",
"ext-xml": "*",
- "symfony/security": "~2.7.38|~2.8.31",
+ "symfony/security": "~2.7.47|~2.8.40",
"symfony/security-acl": "~2.7",
"symfony/http-kernel": "~2.7"
},
diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php
index 62b0071212..376194d3a6 100644
--- a/src/Symfony/Component/Security/Http/Firewall.php
+++ b/src/Symfony/Component/Security/Http/Firewall.php
@@ -47,20 +47,29 @@ class Firewall implements EventSubscriberInterface
}
// 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) {
$this->exceptionListeners[$event->getRequest()] = $exceptionListener;
$exceptionListener->register($this->dispatcher);
}
// initiate the listener chain
- foreach ($listeners as $listener) {
+ foreach ($authenticationListeners as $listener) {
$listener->handle($event);
if ($event->hasResponse()) {
break;
}
}
+
+ if (null !== $logoutListener) {
+ $logoutListener->handle($event);
+ }
}
public function onKernelFinishRequest(FinishRequestEvent $event)
diff --git a/src/Symfony/Component/Security/Http/FirewallMap.php b/src/Symfony/Component/Security/Http/FirewallMap.php
index e767d123cb..fc97410d4e 100644
--- a/src/Symfony/Component/Security/Http/FirewallMap.php
+++ b/src/Symfony/Component/Security/Http/FirewallMap.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
+use Symfony\Component\Security\Http\Firewall\LogoutListener;
/**
* FirewallMap allows configuration of different firewalls for specific parts
@@ -25,9 +26,9 @@ class FirewallMap implements FirewallMapInterface
{
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) {
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);
}
}