Skip to content

Commit 892f00f

Browse files
committed
[HttpKernel] added a URL signer mechanism for hincludes
1 parent a0c49c3 commit 892f00f

File tree

5 files changed

+112
-7
lines changed

5 files changed

+112
-7
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@
3636
<service id="http_content_renderer.strategy.hinclude" class="%http_content_renderer.strategy.hinclude.class%">
3737
<tag name="kernel.content_renderer_strategy" />
3838
<argument type="service" id="templating" />
39+
<argument type="service" id="uri_signer" />
3940
<argument>%http_content_renderer.strategy.hinclude.global_template%</argument>
41+
<call method="setUrlGenerator"><argument type="service" id="router" /></call>
4042
</service>
4143

4244
<!-- FIXME: make the listener registration optional via a configuration setting? -->
4345
<service id="http_content_renderer.listener.router_proxy" class="%http_content_renderer.listener.router_proxy.class%">
4446
<tag name="kernel.event_subscriber" />
47+
<argument type="service" id="uri_signer" />
4548
</service>
4649

4750
</services>

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<parameter key="cache_warmer.class">Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate</parameter>
1212
<parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</parameter>
1313
<parameter key="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter>
14+
<parameter key="uri_signer.class">Symfony\Component\HttpKernel\UriSigner</parameter>
1415
</parameters>
1516

1617
<services>
@@ -51,5 +52,9 @@
5152
<argument type="service" id="kernel" />
5253
<argument>%kernel.root_dir%/Resources</argument>
5354
</service>
55+
56+
<service id="uri_signer" class="%uri_signer.class%">
57+
<argument>%kernel.secret%</argument>
58+
</service>
5459
</services>
5560
</container>

src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
1717
use Symfony\Component\HttpKernel\KernelEvents;
1818
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
19+
use Symfony\Component\HttpKernel\UriSigner;
1920
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2021

2122
/**
@@ -28,6 +29,13 @@
2829
*/
2930
class RouterProxyListener implements EventSubscriberInterface
3031
{
32+
private $signer;
33+
34+
public function __construct(UriSigner $signer)
35+
{
36+
$this->signer = $signer;
37+
}
38+
3139
/**
3240
* Fixes request attributes when the route is '_proxy'.
3341
*
@@ -43,16 +51,22 @@ public function onKernelRequest(GetResponseEvent $event)
4351
return;
4452
}
4553

46-
$this->checkRequest($request);
54+
$this->validateRequest($request);
4755

4856
parse_str($request->query->get('path', ''), $attributes);
4957
$request->attributes->add($attributes);
5058
$request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params'), $attributes));
5159
$request->query->remove('path');
5260
}
5361

54-
protected function checkRequest(Request $request)
62+
protected function validateRequest(Request $request)
5563
{
64+
// is the Request safe?
65+
if (!$request->isMethodSafe()) {
66+
throw new AccessDeniedHttpException();
67+
}
68+
69+
// does the Request come from a trusted IP?
5670
$trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies());
5771
$remoteAddress = $request->server->get('REMOTE_ADDR');
5872
foreach ($trustedIps as $ip) {
@@ -61,6 +75,11 @@ protected function checkRequest(Request $request)
6175
}
6276
}
6377

78+
// is the Request signed?
79+
if ($this->signer->check($request->getUri())) {
80+
return;
81+
}
82+
6483
throw new AccessDeniedHttpException();
6584
}
6685

src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,40 @@
1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\Templating\EngineInterface;
1616
use Symfony\Component\HttpKernel\Controller\ControllerReference;
17+
use Symfony\Component\HttpKernel\UriSigner;
1718

1819
/**
1920
*
2021
* @author Fabien Potencier <fabien@symfony.com>
2122
*/
22-
class HIncludeRenderingStrategy implements RenderingStrategyInterface
23+
class HIncludeRenderingStrategy extends GeneratorAwareRenderingStrategy
2324
{
2425
private $templating;
2526
private $globalDefaultTemplate;
27+
private $signer;
2628

27-
public function __construct($templating, $globalDefaultTemplate = null)
29+
public function __construct($templating, UriSigner $signer = null, $globalDefaultTemplate = null)
2830
{
2931
if (!$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) {
3032
throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of \Twig_Environment or Symfony\Component\Templating\EngineInterface');
3133
}
3234

3335
$this->templating = $templating;
3436
$this->globalDefaultTemplate = $globalDefaultTemplate;
37+
$this->signer = $signer;
3538
}
3639

3740
public function render($uri, Request $request = null, array $options = array())
3841
{
3942
if ($uri instanceof ControllerReference) {
40-
// FIXME: can we sign the proxy URL instead?
41-
throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy.');
43+
if (null === $this->signer) {
44+
throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.');
45+
}
46+
47+
$uri = $this->signer->sign($this->generateProxyUri($uri, $request));
4248
}
4349

44-
$defaultTemplate = $options['default'] ?: null;
50+
$defaultTemplate = isset($options['default']) ? $options['default'] : null;
4551
$defaultContent = null;
4652

4753
if (null !== $defaultTemplate) {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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\Component\HttpKernel;
13+
14+
/**
15+
* UriSigner.
16+
*
17+
* @author Fabien Potencier <fabien@symfony.com>
18+
*/
19+
class UriSigner
20+
{
21+
private $secret;
22+
23+
/**
24+
* Constructor.
25+
*
26+
* @param string $secret A secret
27+
*/
28+
public function __construct($secret)
29+
{
30+
$this->secret = $secret;
31+
}
32+
33+
/**
34+
* Signs a URI.
35+
*
36+
* The given URI is signed by adding a _hash query string parameter
37+
* which value depends on the URI and the secret.
38+
*
39+
* @param string $uri A URI to sign
40+
*
41+
* @return string The signed URI
42+
*/
43+
public function sign($uri)
44+
{
45+
return $uri.(false === (strpos($uri, '?')) ? '?' : '&').'_hash='.$this->computeHash($uri);
46+
}
47+
48+
/**
49+
* Checks that a URI contains the correct hash.
50+
*
51+
* @param string $uri A signed URI
52+
*
53+
* @return Boolean True if the URI is signed correctly, false otherwise
54+
*/
55+
public function check($uri)
56+
{
57+
if (!preg_match('/(\?|&)_hash=(.+?)(&|$)/', $uri, $matches, PREG_OFFSET_CAPTURE)) {
58+
return false;
59+
}
60+
61+
// the naked URI is the URI without the _hash parameter (we need to keep the ? if there is some other parameters after)
62+
$offset = ('?' == $matches[1][0] && '&' != $matches[3][0]) ? 0 : 1;
63+
$nakedUri = substr($uri, 0, $matches[0][1] + $offset).substr($uri, $matches[0][1] + strlen($matches[0][0]));
64+
65+
return $this->computeHash($nakedUri) === $matches[2][0];
66+
}
67+
68+
private function computeHash($uri)
69+
{
70+
return urlencode(base64_encode(hash_hmac('sha1', $uri, $this->secret, true)));
71+
}
72+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy