[SecurityBundle] Templating helpers to generate logout URL's with CSRF tokens

As each firewall is configured, its logout listener (if any) will be registered with the LogoutUrlHelper service. In a template, this helper may be used to generate relative or absolute URL's to a particular firewall's logout path. A CSRF token will be appended to the URL as necessary.

The Twig extension composes the helper service to avoid code duplication (see: #2999).
This commit is contained in:
Jeremy Mikola 2011-12-30 21:43:06 -05:00
parent aaaa04003d
commit 66722b3d2e
8 changed files with 240 additions and 3 deletions

View File

@ -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

View File

@ -5,10 +5,17 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="templating.helper.logout_url.class">Symfony\Bundle\SecurityBundle\Templating\Helper\LogoutUrlHelper</parameter>
<parameter key="templating.helper.security.class">Symfony\Bundle\SecurityBundle\Templating\Helper\SecurityHelper</parameter>
</parameters>
<services>
<service id="templating.helper.logout_url" class="%templating.helper.logout_url.class%">
<tag name="templating.helper" alias="logout_url" />
<argument type="service" id="request" strict="false" />
<argument type="service" id="router" />
</service>
<service id="templating.helper.security" class="%templating.helper.security.class%">
<tag name="templating.helper" alias="security" />
<argument type="service" id="security.context" on-invalid="ignore" />

View File

@ -5,9 +5,16 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="twig.extension.logout_url.class">Symfony\Bundle\SecurityBundle\Twig\Extension\LogoutUrlExtension</parameter>
<parameter key="twig.extension.security.class">Symfony\Bundle\SecurityBundle\Twig\Extension\SecurityExtension</parameter>
</parameters>
<services>
<service id="twig.extension.logout_url" class="%twig.extension.logout_url.class%" public="false">
<tag name="twig.extension" />
<argument type="service" id="templating.helper.logout_url" />
</service>
<service id="twig.extension.security" class="%twig.extension.security.class%" public="false">
<tag name="twig.extension" />
<argument type="service" id="security.context" on-invalid="ignore" />

View File

@ -0,0 +1,119 @@
<?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\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 <jmikola@gmail.com>
*/
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';
}
}

View File

@ -2,5 +2,7 @@
{% block body %}
Hello {{ app.user.username }}!<br /><br />
You're browsing to path "{{ app.request.pathInfo }}".
You're browsing to path "{{ app.request.pathInfo }}".<br /><br />
<a href="{{ logout_path('default') }}">Log out</a>.
<a href="{{ logout_url('default') }}">Log out</a>.
{% endblock %}

View File

@ -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(), '/');
}
/**

View File

@ -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 }

View File

@ -0,0 +1,75 @@
<?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\Twig\Extension;
use Symfony\Bundle\SecurityBundle\Templating\Helper\LogoutUrlHelper;
/**
* LogoutUrlHelper provides generator functions for the logout URL to Twig.
*
* @author Jeremy Mikola <jmikola@gmail.com>
*/
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';
}
}