8000 [HttpKernel] added a URL signer mechanism for hincludes · symfony/symfony@892f00f · GitHub
[go: up one dir, main page]

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)
0