diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
index abdca9a2e9..41060baf97 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
@@ -312,6 +312,18 @@ class SecurityExtension extends Extension
foreach ($firewall['logout']['handlers'] as $handlerId) {
$listener->addMethodCall('addHandler', array(new Reference($handlerId)));
}
+
+ // register with LogoutUrlHelper
+ $container
+ ->getDefinition('templating.helper.logout_url')
+ ->addMethodCall('registerListener', array(
+ $id,
+ $firewall['logout']['path'],
+ $firewall['logout']['intention'],
+ $firewall['logout']['csrf_parameter'],
+ isset($firewall['logout']['csrf_provider']) ? new Reference($firewall['logout']['csrf_provider']) : null,
+ ))
+ ;
}
// Authentication listeners
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_php.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_php.xml
index f2c2b121c8..83cb5597c1 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_php.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_php.xml
@@ -5,10 +5,17 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+ Symfony\Bundle\SecurityBundle\Templating\Helper\LogoutUrlHelper
Symfony\Bundle\SecurityBundle\Templating\Helper\SecurityHelper
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.xml
index 14b0414d06..5271c1434e 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.xml
@@ -5,9 +5,16 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+ Symfony\Bundle\SecurityBundle\Twig\Extension\LogoutUrlExtension
Symfony\Bundle\SecurityBundle\Twig\Extension\SecurityExtension
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php
new file mode 100644
index 0000000000..0d5229c928
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\SecurityBundle\Templating\Helper;
+
+use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Templating\Helper\Helper;
+
+/**
+ * LogoutUrlHelper provides generator functions for the logout URL.
+ *
+ * @author Jeremy Mikola
+ */
+class LogoutUrlHelper extends Helper
+{
+ private $listeners;
+ private $request;
+ private $router;
+
+ /**
+ * Constructor.
+ *
+ * @param Request $request A request instance
+ * @param UrlGeneratorInterface $router A Router instance
+ */
+ public function __construct(Request $request, UrlGeneratorInterface $router)
+ {
+ $this->request = $request;
+ $this->router = $router;
+ $this->listeners = array();
+ }
+
+ /**
+ * Registers a firewall's LogoutListener, allowing its URL to be generated.
+ *
+ * @param string $key The firewall key
+ * @param string $logoutPath The path that starts the logout process
+ * @param string $intention The intention for CSRF token generation
+ * @param string $csrfParameter The CSRF token parameter name
+ * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance
+ */
+ public function registerListener($key, $logoutPath, $intention, $csrfParameter, CsrfProviderInterface $csrfProvider = null)
+ {
+ $this->listeners[$key] = array($logoutPath, $intention, $csrfParameter, $csrfProvider);
+ }
+
+ /**
+ * Generate the relative logout URL for the firewall.
+ *
+ * @param string $key The firewall key
+ * @return string The relative logout URL
+ */
+ public function getLogoutPath($key)
+ {
+ return $this->generateLogoutUrl($key, false);
+ }
+
+ /**
+ * Generate the absolute logout URL for the firewall.
+ *
+ * @param string $key The firewall key
+ * @return string The absolute logout URL
+ */
+ public function getLogoutUrl($key)
+ {
+ return $this->generateLogoutUrl($key, true);
+ }
+
+ /**
+ * Generate the logout URL for the firewall.
+ *
+ * @param string $key The firewall key
+ * @param Boolean $absolute Whether to generate an absolute URL
+ * @return string The logout URL
+ * @throws InvalidArgumentException if no LogoutListener is registered for the key
+ */
+ private function generateLogoutUrl($key, $absolute)
+ {
+ if (!array_key_exists($key, $this->listeners)) {
+ throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key));
+ }
+
+ list($logoutPath, $intention, $csrfParameter, $csrfProvider) = $this->listeners[$key];
+
+ $parameters = null !== $csrfProvider ? array($csrfParameter => $csrfProvider->generateCsrfToken($intention)) : array();
+
+ if ('/' === $logoutPath[0]) {
+ $url = ($absolute ? $this->request->getUriForPath($logoutPath) : $this->request->getBasePath() . $logoutPath);
+
+ if (!empty($parameters)) {
+ $url .= '?' . http_build_query($parameters);
+ }
+ } else {
+ $url = $this->router->generate($logoutPath, $parameters, $absolute);
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns the canonical name of this helper.
+ *
+ * @return string The canonical name
+ */
+ public function getName()
+ {
+ return 'logout_url';
+ }
+}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig
index 9972b48ae7..b56c186ecb 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig
@@ -2,5 +2,7 @@
{% block body %}
Hello {{ app.user.username }}!
- You're browsing to path "{{ app.request.pathInfo }}".
+ You're browsing to path "{{ app.request.pathInfo }}".
+ Log out.
+ Log out.
{% endblock %}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php
index 52b533c591..1440c79425 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php
@@ -19,7 +19,7 @@ class CsrfFormLoginTest extends WebTestCase
/**
* @dataProvider getConfigs
*/
- public function testFormLogin($config)
+ public function testFormLoginAndLogoutWithCsrfTokens($config)
{
$client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config));
$client->insulate();
@@ -31,9 +31,20 @@ class CsrfFormLoginTest extends WebTestCase
$this->assertRedirect($client->getResponse(), '/profile');
- $text = $client->followRedirect()->text();
+ $crawler = $client->followRedirect();
+
+ $text = $crawler->text();
$this->assertContains('Hello johannes!', $text);
$this->assertContains('You\'re browsing to path "/profile".', $text);
+
+ $logoutLinks = $crawler->selectLink('Log out')->links();
+ $this->assertEquals(2, count($logoutLinks));
+ $this->assertContains('_csrf_token=', $logoutLinks[0]->getUri());
+ $this->assertSame($logoutLinks[0]->getUri(), $logoutLinks[1]->getUri());
+
+ $client->click($logoutLinks[0]);
+
+ $this->assertRedirect($client->getResponse(), '/');
}
/**
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml
index a4d77c2c3e..1f1fa85d0a 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml
@@ -38,6 +38,10 @@ security:
csrf_parameter: "user_login[_token]"
csrf_provider: form.csrf_provider
anonymous: ~
+ logout:
+ path: /logout_path
+ target: /
+ csrf_provider: form.csrf_provider
access_control:
- { path: .*, roles: IS_AUTHENTICATED_FULLY }
diff --git a/src/Symfony/Bundle/SecurityBundle/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bundle/SecurityBundle/Twig/Extension/LogoutUrlExtension.php
new file mode 100644
index 0000000000..bad5e79ee8
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Twig/Extension/LogoutUrlExtension.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\SecurityBundle\Twig\Extension;
+
+use Symfony\Bundle\SecurityBundle\Templating\Helper\LogoutUrlHelper;
+
+/**
+ * LogoutUrlHelper provides generator functions for the logout URL to Twig.
+ *
+ * @author Jeremy Mikola
+ */
+class LogoutUrlExtension extends \Twig_Extension
+{
+ private $helper;
+
+ /**
+ * Constructor.
+ *
+ * @param LogoutUrlHelper $helper
+ */
+ public function __construct(LogoutUrlHelper $helper)
+ {
+ $this->helper = $helper;
+ }
+
+ /**
+ * @see Twig_Extension::getFunctions()
+ */
+ public function getFunctions()
+ {
+ return array(
+ 'logout_url' => new \Twig_Function_Method($this, 'getLogoutUrl'),
+ 'logout_path' => new \Twig_Function_Method($this, 'getLogoutPath'),
+ );
+ }
+
+ /**
+ * Generate the relative logout URL for the firewall.
+ *
+ * @param string $key The firewall key
+ * @return string The relative logout URL
+ */
+ public function getLogoutPath($key)
+ {
+ return $this->helper->getLogoutPath($key);
+ }
+
+ /**
+ * Generate the absolute logout URL for the firewall.
+ *
+ * @param string $key The firewall key
+ * @return string The absolute logout URL
+ */
+ public function getLogoutUrl($key)
+ {
+ return $this->helper->getLogoutUrl($key);
+ }
+
+ /**
+ * @see Twig_ExtensionInterface::getName()
+ */
+ public function getName()
+ {
+ return 'logout_url';
+ }
+}