8000 [SecurityBundle] Templating helpers to generate logout URL's with CSR… · symfony/symfony@66722b3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 66722b3

Browse files
committed
[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).
1 parent aaaa040 commit 66722b3

File tree

8 files changed

+240
-3
lines changed

8 files changed

+240
-3
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,18 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
312312
foreach ($firewall['logout']['handlers'] as $handlerId) {
313313
$listener->addMethodCall('addHandler', array(new Reference($handlerId)));
314314
}
315+
316+
// register with LogoutUrlHelper
317+
$container
318+
->getDefinition('templating.helper.logout_url')
319+
->addMethodCall('registerListener', array(
320+
$id,
321+
$firewall['logout']['path'],
322+
$firewall['logout']['intention'],
323+
$firewall['logout']['csrf_parameter'],
324+
isset($firewall['logout']['csrf_provider']) ? new Reference($firewall['logout']['csrf_provider']) : null,
325+
))
326+
;
315327
}
316328

317329
// Authentication listeners

src/Symfony/Bundle/SecurityBundle/Resources/config/templating_php.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<parameters>
8+
<parameter key="templating.helper.logout_url.class">Symfony\Bundle\SecurityBundle\Templating\Helper\LogoutUrlHelper</parameter>
89
<parameter key="templating.helper.security.class">Symfony\Bundle\SecurityBundle\Templating\Helper\SecurityHelper</parameter>
910
</parameters>
1011

1112
<services>
13+
<service id="templating.helper.logout_url" class="%templating.helper.logout_url.class%">
14+
<tag name="templating.helper" alias="logout_url" />
15+
<argument type="service" id="request" strict="false" />
16+
<argument type="service" id="router" />
17+
</service>
18+
1219
<service id="templating.helper.security" class="%templating.helper.security.class%">
1320
<tag name="templating.helper" alias="security" />
1421
<argument type="service" id="security.context" on-invalid="ignore" />

src/Symfony/Bundle/SecurityBundle/Resources/config/templating_twig.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<parameters>
8+
<parameter key="twig.extension.logout_url.class">Symfony\Bundle\SecurityBundle\Twig\Extension\LogoutUrlExtension</parameter>
89
<parameter key="twig.extension.security.class">Symfony\Bundle\SecurityBundle\Twig\Extension\SecurityExtension</parameter>
910
</parameters>
11+
1012
<services>
13+
<service id="twig.extension.logout_url" class="%twig.extension.logout_url.class%" public="false">
14+
<tag name="twig.extension" />
15+
<argument type="service" id="templating.helper.logout_url" />
16+
</service>
17+
1118
<service id="twig.extension.security" class="%twig.extension.security.class%" public="false">
1219
<tag name="twig.extension" />
1320
<argument type="service" id="security.context" on-invalid="ignore" />
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Templating\Helper;
13+
14+
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
17+
use Symfony\Component\Templating\Helper\Helper;
18+
19+
/**
20+
* LogoutUrlHelper provides generator functions for the logout URL.
21+
*
22+
* @author Jeremy Mikola <jmikola@gmail.com>
23+
*/
24+
class LogoutUrlHelper extends Helper
25+
{
26+
private $listeners;
27+
private $request;
28+
private $router;
29+
30+
/**
31+
* Constructor.
32+
*
33+
* @param Request $request A request instance
34+
* @param UrlGeneratorInterface $router A Router instance
35+
*/
36+
public function __construct(Request $request, UrlGeneratorInterface $router)
37+
{
38+
$this->request = $request;
39+
$this->router = $router;
40+
$this->listeners = array();
41+
}
42+
43+
/**
44+
* Registers a firewall's LogoutListener, allowing its URL to be generated.
45+
*
46+
* @param string $key The firewall key
47+
* @param string $logoutPath The path that starts the logout process
48+
* @param string $intention The intention for CSRF token generation
49+
* @param string $csrfParameter The CSRF token parameter name
50+
* @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance
51+
*/
52+
public function registerListener($key, $logoutPath, $intention, $csrfParameter, CsrfProviderInterface $csrfProvider = null)
53+
{
54+
$this->listeners[$key] = array($logoutPath, $intention, $csrfParameter, $csrfProvider);
55+
}
56+
57+
/**
58+
* Generate the relative logout URL for the firewall.
59+
*
60+
* @param string $key The firewall key
61+
* @return string The relative logout URL
62+
*/
63+
public function getLogoutPath($key)
64+
{
65+
return $this->generateLogoutUrl($key, false);
66+
}
67+
68+
/**
69+
* Generate the absolute logout URL for the firewall.
70+
*
71+
* @param string $key The firewall key
72+
* @return string The absolute logout URL
73+
*/
74+
public function getLogoutUrl($key)
75+
{
76+
return $this->generateLogoutUrl($key, true);
77+
}
78+
79+
/**
80+
* Generate the logout URL for the firewall.
81+
*
82+
* @param string $key The firewall key
83+
* @param Boolean $absolute Whether to generate an absolute URL
84+
* @return string The logout URL
85+
* @throws InvalidArgumentException if no LogoutListener is registered for the key
86+
*/
87+
private function generateLogoutUrl($key, $absolute)
88+
{
89+
if (!array_key_exists($key, $this->listeners)) {
90+
throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key));
91+
}
92+
93+
list($logoutPath, $intention, $csrfParameter, $csrfProvider) = $this->listeners[$key];
94+
95+
$parameters = null !== $csrfProvider ? array($csrfParameter => $csrfProvider->generateCsrfToken($intention)) : array();
96+
97+
if ('/' === $logoutPath[0]) {
98+
$url = ($absolute ? $this->request->getUriForPath($logoutPath) : $this->request->getBasePath() . $logoutPath);
99+
100+
if (!empty($parameters)) {
101+
$url .= '?' . http_build_query($parameters);
102+
}
103+
} else {
104+
$url = $this->router->generate($logoutPath, $parameters, $absolute);
105+
}
106+
107+
return $url;
108+
}
109+
110+
/**
111+
* Returns the canonical name of this helper.
112+
*
113+
* @return string The canonical name
114+
*/
115+
public function getName()
116+
{
117+
return 'logout_url';
118+
}
119+
}

src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22

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

src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class CsrfFormLoginTest extends WebTestCase
1919
/**
2020
* @dataProvider getConfigs
2121
*/
22-
public function testFormLogin($config)
22+
public function testFormLoginAndLogoutWithCsrfTokens($config)
2323
{
2424
$client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config));
2525
$client->insulate();
@@ -31,9 +31,20 @@ public function testFormLogin($config)
3131

3232
$this->assertRedirect($client->getResponse(), '/profile');
3333

34-
$text = $client->followRedirect()->text();
34+
$crawler = $client->followRedirect();
35+
36+
$text = $crawler->text();
3537
$this->assertContains('Hello johannes!', $text);
3638
$this->assertContains('You\'re browsing to path "/profile".', $text);
39+
40+
$logoutLinks = $crawler->selectLink('Log out')->links();
41+
$this->assertEquals(2, count($logoutLinks));
42+
$this->assertContains('_csrf_token=', $logoutLinks[0]->getUri());
43+
$this->assertSame($logoutLinks[0]->getUri(), $logoutLinks[1]->getUri());
44+
45+
$client->click($logoutLinks[0]);
46+
47+
$this->assertRedirect($client->getResponse(), '/');
3748
}
3849

3950
/**

src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ security:
3838
csrf_parameter: "user_login[_token]"
3939
csrf_provider: form.csrf_provider
4040
anonymous: ~
41+
logout:
42+
path: /logout_path
43+
target: /
44+
csrf_provider: form.csrf_provider
4145

4246
access_control:
4347
- { path: .*, roles: IS_AUTHENTICATED_FULLY }
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Twig\Extension;
13+
14+
use Symfony\Bundle\SecurityBundle\Templating\Helper\LogoutUrlHelper;
15+
16+
/**
17+
* LogoutUrlHelper provides generator functions for the logout URL to Twig.
18+
*
19+
* @author Jeremy Mikola <jmikola@gmail.com>
20+
*/
21+
class LogoutUrlExtension extends \Twig_Extension
22+
{
23+
private $helper;
24+
25+
/**
26+
* Constructor.
27+
*
28+
* @param LogoutUrlHelper $helper
29+
*/
30+
public function __construct(LogoutUrlHelper $helper)
31+
{
32+
$this->helper = $helper;
33+
}
34+
35+
/**
36+
* @see Twig_Extension::getFunctions()
37+
*/
38+
public function getFunctions()
39+
{
40+
return array(
41+
'logout_url' => new \Twig_Function_Method($this, 'getLogoutUrl'),
42+
'logout_path' => new \Twig_Function_Method($this, 'getLogoutPath'),
43+
);
44+
}
45+
46+
/**
47+
* Generate the relative logout URL for the firewall.
48+
*
49+
* @param string $key The firewall key
50+
* @return string The relative logout URL
51+
*/
52+
public function getLogoutPath($key)
53+
{
54+
return $this->helper->getLogoutPath($key);
55+
}
56+
57+
/**
58+
* Generate the absolute logout URL for the firewall.
59+
*
60+
* @param string $key The firewall key
61+
* @return string The absolute logout URL
62+
*/
63+
public function getLogoutUrl($key)
64+
{
65+
return $this->helper->getLogoutUrl($key);
66+
}
67+
68+
/**
69+
* @see Twig_ExtensionInterface::getName()
70+
*/
71+
public function getName()
72+
{
73+
return 'logout_url';
74+
}
75+
}

0 commit comments

Comments
 (0)
0