瀏覽代碼

logout refactoring

Johannes M. Schmitt 14 年之前
父節點
當前提交
d94420f3a5

+ 3 - 3
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php

@@ -20,9 +20,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
  */
 interface SecurityFactoryInterface
 {
-    public function create(ContainerBuilder $container, $id, $config, $userProvider, $providerIds, $defaultEntryPoint);
+    function create(ContainerBuilder $container, $id, $config, $userProvider, $providerIds, $defaultEntryPoint);
 
-    public function getPosition();
+    function getPosition();
 
-    public function getKey();
+    function getKey();
 }

+ 22 - 3
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/SecurityExtension.php

@@ -203,14 +203,31 @@ class SecurityExtension extends Extension
 
         // Logout listener
         if (array_key_exists('logout', $firewall)) {
-            $listeners[] = new Reference('security.logout_listener');
+            $listenerId = 'security.logout_listener.'.$id;
+            $listener = $container->setDefinition($listenerId, clone $container->getDefinition('security.logout_listener'));          
+            
+            $listeners[] = new Reference($listenerId);
 
+            $arguments = $listener->getArguments();
             if (isset($firewall['logout']['path'])) {
-                $container->setParameter('security.logout.path', $firewall['logout']['path']);
+                $arguments[1] = $firewall['logout']['path'];
             }
 
             if (isset($firewall['logout']['target'])) {
-                $container->setParameter('security.logout.target_path', $firewall['logout']['target']);
+                $arguments[2] = $firewall['logout']['target'];
+            }
+            $listener->setArguments($arguments);
+            
+            if (!isset($firewall['stateless']) || !$firewall['stateless']) {
+                $listener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session')));
+            }
+            
+            if (count($cookies = $this->fixConfig($firewall['logout'], 'cookie')) > 0) {
+                $cookieHandlerId = 'security.logout.handler.cookie_clearing.'.$id;
+                $cookieHandler = $container->setDefinition($cookieHandlerId, clone $container->getDefinition('security.logout.handler.cookie_clearing'));
+                $cookieHandler->setArguments(array($cookies));
+
+                $listener->addMethodCall('addHandler', array(new Reference($cookieHandlerId)));
             }
         }
 
@@ -426,6 +443,8 @@ class SecurityExtension extends Extension
         return $listenerId;
     }
 
+    
+    
     protected function createExceptionListener($container, $id, $defaultEntryPoint)
     {
         $exceptionListenerId = 'security.exception_listener.'.$id;

+ 4 - 6
src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml

@@ -49,6 +49,8 @@
         <parameter key="security.logout_listener.class">Symfony\Component\HttpKernel\Security\Firewall\LogoutListener</parameter>
         <parameter key="security.logout.path">/logout</parameter>
         <parameter key="security.logout.target_path">/</parameter>
+        <parameter key="security.logout.handler.session.class">Symfony\Component\HttpKernel\Security\Logout\SessionLogoutHandler</parameter>
+        <parameter key="security.logout.handler.cookie_clearing.class">Symfony\Component\HttpKernel\Security\Logout\CookieClearingLogoutHandler</parameter>
 
         <parameter key="security.authentication.switchuser_listener.class">Symfony\Component\HttpKernel\Security\Firewall\SwitchUserListener</parameter>
         <parameter key="security.authentication.switchuser.role">ROLE_ALLOWED_TO_SWITCH</parameter>
@@ -91,6 +93,8 @@
 
         <service id="security.encoder.plain" class="%security.encoder.plain.class%" />
 
+        <service id="security.logout.handler.session" class="%security.logout.handler.session.class%"></service>
+
         <service id="security.authentication.listener.anonymous" class="%security.authentication.listener.anonymous.class%">
             <argument type="service" id="security.context" />
             <argument>%security.anonymous.key%</argument>
@@ -113,12 +117,6 @@
             <argument>%security.authentication.digest_entry_point.key%</argument>
         </service>
 
-        <service id="security.logout_listener" class="%security.logout_listener.class%">
-            <argument type="service" id="security.context" />
-            <argument>%security.logout.path%</argument>
-            <argument>%security.logout.target_path%</argument>
-        </service>
-
         <service id="security.channel_listener" class="%security.channel_listener.class%">
             <argument type="service" id="security.access_map" />
             <argument type="service" id="security.authentication.retry_entry_point" />

+ 10 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/config/security_templates.xml

@@ -20,6 +20,16 @@
         <service id="security.authentication.factory.digest" class="Symfony\Bundle\FrameworkBundle\DependencyInjection\Security\Factory\HttpDigestFactory">
             <tag name="security.listener.factory" />
         </service>
+        
+        <service id="security.logout_listener" class="%security.logout_listener.class%">
+            <argument type="service" id="security.context" />
+            <argument>%security.logout.path%</argument>
+            <argument>%security.logout.target_path%</argument>
+        </service>
+        
+        <service id="security.logout.handler.cookie_clearing" class="%security.logout.handler.cookie_clearing.class%">
+            <argument type="collection"></argument>
+        </service>        
 
         <service id="security.authentication.listener.form" class="%security.authentication.listener.form.class%">
             <argument type="service" id="security.context" />

+ 25 - 6
src/Symfony/Component/HttpKernel/Security/Firewall/LogoutListener.php

@@ -2,6 +2,7 @@
 
 namespace Symfony\Component\HttpKernel\Security\Firewall;
 
+use Symfony\Component\HttpKernel\Security\Logout\LogoutHandlerInterface;
 use Symfony\Component\Security\SecurityContext;
 use Symfony\Component\EventDispatcher\EventDispatcher;
 use Symfony\Component\EventDispatcher\Event;
@@ -26,10 +27,12 @@ class LogoutListener implements ListenerInterface
     protected $securityContext;
     protected $logoutPath;
     protected $targetUrl;
+    protected $handlers;    
 
     /**
      * Constructor
      *
+     * @param SecurityContext $securityContext
      * @param string $logoutPath The path that starts the logout process
      * @param string $targetUrl  The URL to redirect to after logout
      */
@@ -38,7 +41,19 @@ class LogoutListener implements ListenerInterface
         $this->securityContext = $securityContext;
         $this->logoutPath = $logoutPath;
         $this->targetUrl = $targetUrl;
+        $this->handlers = array();
     }
+    
+    /**
+     * Adds a logout handler
+     * 
+     * @param LogoutHandlerInterface $handler
+     * @return void
+     */
+    public function addHandler(LogoutHandlerInterface $handler)
+    {
+        $this->handlers[] = $handler;
+    }    
 
     /**
      * Registers a core.security listener.
@@ -59,7 +74,7 @@ class LogoutListener implements ListenerInterface
     }
     
     /**
-     * 
+     * Performs the logout if requested
      *
      * @param Event $event An Event instance
      */
@@ -70,13 +85,17 @@ class LogoutListener implements ListenerInterface
         if ($this->logoutPath !== $request->getPathInfo()) {
             return;
         }
-
-        $this->securityContext->setToken(null);
-        $request->getSession()->invalidate();
-
+        
         $response = new Response();
         $response->setRedirect(0 !== strpos($this->targetUrl, 'http') ? $request->getUriForPath($this->targetUrl) : $this->targetUrl, 302);
-
+        
+        $token = $this->securityContext->getToken();
+        
+        foreach ($this->handlers as $handler) {
+            $handler->logout($request, $response, $token);
+        }
+        
+        $this->securityContext->setToken(null);
         $event->setReturnValue($response);
 
         return true;

+ 61 - 0
src/Symfony/Component/HttpKernel/Security/Logout/CookieClearingLogoutHandler.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Symfony\Component\HttpKernel\Security\Logout;
+
+use Symfony\Component\Security\Authentication\Token\TokenInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * This handler cleares the passed cookies when a user logs out.
+ * 
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class CookieClearingLogoutHandler implements LogoutHandlerInterface
+{
+    protected $cookieNames;
+    
+    /**
+     * Constructor
+     * @param array $cookieNames An array of cookie names to unset
+     */
+    public function __construct(array $cookieNames)
+    {
+        $this->cookieNames = $cookieNames;
+    }
+    
+    /**
+     * Returns the names of the cookies to unset
+     * @return array
+     */
+    public function getCookieNames()
+    {
+        return $this->cookieNames;
+    }
+    
+    /**
+     * Implementation for the LogoutHandlerInterface. Deletes all requested cookies.
+     * 
+     * @param Request $request
+     * @param Response $response
+     * @param TokenInterface $token
+     * @return void
+     */
+    public function logout(Request $request, Response $response, TokenInterface $token)
+    {
+        $expires = time() - 86400;
+        
+        foreach ($this->cookieNames as $cookieName) {
+            $response->headers->setCookie($cookieName, '', null, $expires);
+        }
+    }
+}

+ 36 - 0
src/Symfony/Component/HttpKernel/Security/Logout/LogoutHandlerInterface.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Symfony\Component\HttpKernel\Security\Logout;
+
+use Symfony\Component\Security\Authentication\Token\TokenInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface that needs to be implemented by LogoutHandlers.
+ * 
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+interface LogoutHandlerInterface
+{
+    /**
+     * This method is called by the LogoutListener when a user has requested
+     * to be logged out. Usually, you would unset session variables, or remove
+     * cookies, etc.
+     * 
+     * @param Request $request
+     * @param Response $response
+     * @param TokenInterface $token
+     * @return void
+     */
+    function logout(Request $request, Response $response, TokenInterface $token);
+}

+ 37 - 0
src/Symfony/Component/HttpKernel/Security/Logout/SessionLogoutHandler.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Symfony\Component\HttpKernel\Security\Logout;
+
+use Symfony\Component\Security\Authentication\Token\TokenInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Handler for clearing invalidating the current session.
+ * 
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class SessionLogoutHandler implements LogoutHandlerInterface
+{
+    /**
+     * Invalidate the current session
+     * 
+     * @param Request $request
+     * @param Response $response
+     * @param TokenInterface $token
+     * @return void
+     */
+    public function logout(Request $request, Response $response, TokenInterface $token)
+    {
+        $request->getSession()->invalidate();
+    }    
+}

+ 42 - 0
tests/Symfony/Tests/Component/HttpKernel/Security/Logout/CookieClearingLogoutHandlerTest.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Symfony\Tests\Component\HttpKernel\Security\Logout;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Security\Logout\CookieClearingLogoutHandler;
+
+class CookieClearingLogoutHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testConstructor()
+    {
+        $cookieNames = array('foo', 'foo2', 'foo3');
+        
+        $handler = new CookieClearingLogoutHandler($cookieNames);
+        
+        $this->assertEquals($cookieNames, $handler->getCookieNames());
+    }
+    
+    public function testLogout()
+    {
+        $request = new Request();
+        $response = new Response();
+        $token = $this->getMock('Symfony\Component\Security\Authentication\Token\TokenInterface');
+        
+        $handler = new CookieClearingLogoutHandler(array('foo', 'foo2'));
+        
+        $this->assertFalse($response->headers->has('Set-Cookie'));
+        
+        $handler->logout($request, $response, $token);
+        
+        $headers = $response->headers->all();
+        $cookies = $headers['set-cookie'];
+        $this->assertEquals(2, count($cookies));
+        
+        $cookie = $cookies[0];
+        $this->assertStringStartsWith('foo=;', $cookie);
+        
+        $cookie = $cookies[1];
+        $this->assertStringStartsWith('foo2=;', $cookie);
+    }
+}

+ 31 - 0
tests/Symfony/Tests/Component/HttpKernel/Security/Logout/SessionLogoutHandlerTest.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Symfony\Tests\Component\HttpKernel\Security\Logout;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Security\Logout\SessionLogoutHandler;
+
+class SessionLogoutHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testLogout()
+    {
+        $handler = new SessionLogoutHandler();
+        
+        $request = $this->getMock('Symfony\Component\HttpFoundation\Request');
+        $response = new Response();
+        $session = $this->getMock('Symfony\Component\HttpFoundation\Session', array(), array(), '', false);
+        
+        $request
+            ->expects($this->once())
+            ->method('getSession')
+            ->will($this->returnValue($session))
+        ;
+        
+        $session
+            ->expects($this->once())
+            ->method('invalidate')
+        ;
+        
+        $handler->logout($request, $response, $this->getMock('Symfony\Component\Security\Authentication\Token\TokenInterface'));
+    }
+}