[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:
parent
aaaa04003d
commit
66722b3d2e
@ -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
|
||||
|
@ -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" />
|
||||
|
@ -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" />
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
@ -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 %}
|
||||
|
@ -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(), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 }
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user