[HttpKernel] added a URL signer mechanism for hincludes

This commit is contained in:
Fabien Potencier 2013-01-03 19:22:22 +01:00
parent a0c49c3a94
commit 892f00ffee
5 changed files with 112 additions and 7 deletions

View File

@ -36,12 +36,15 @@
<service id="http_content_renderer.strategy.hinclude" class="%http_content_renderer.strategy.hinclude.class%">
<tag name="kernel.content_renderer_strategy" />
<argument type="service" id="templating" />
<argument type="service" id="uri_signer" />
<argument>%http_content_renderer.strategy.hinclude.global_template%</argument>
<call method="setUrlGenerator"><argument type="service" id="router" /></call>
</service>
<!-- FIXME: make the listener registration optional via a configuration setting? -->
<service id="http_content_renderer.listener.router_proxy" class="%http_content_renderer.listener.router_proxy.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="uri_signer" />
</service>
</services>

View File

@ -11,6 +11,7 @@
<parameter key="cache_warmer.class">Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate</parameter>
<parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</parameter>
<parameter key="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter>
<parameter key="uri_signer.class">Symfony\Component\HttpKernel\UriSigner</parameter>
</parameters>
<services>
@ -51,5 +52,9 @@
<argument type="service" id="kernel" />
<argument>%kernel.root_dir%/Resources</argument>
</service>
<service id="uri_signer" class="%uri_signer.class%">
<argument>%kernel.secret%</argument>
</service>
</services>
</container>

View File

@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\IpUtils;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\UriSigner;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
@ -28,6 +29,13 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class RouterProxyListener implements EventSubscriberInterface
{
private $signer;
public function __construct(UriSigner $signer)
{
$this->signer = $signer;
}
/**
* Fixes request attributes when the route is '_proxy'.
*
@ -43,7 +51,7 @@ class RouterProxyListener implements EventSubscriberInterface
return;
}
$this->checkRequest($request);
$this->validateRequest($request);
parse_str($request->query->get('path', ''), $attributes);
$request->attributes->add($attributes);
@ -51,8 +59,14 @@ class RouterProxyListener implements EventSubscriberInterface
$request->query->remove('path');
}
protected function checkRequest(Request $request)
protected function validateRequest(Request $request)
{
// is the Request safe?
if (!$request->isMethodSafe()) {
throw new AccessDeniedHttpException();
}
// does the Request come from a trusted IP?
$trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies());
$remoteAddress = $request->server->get('REMOTE_ADDR');
foreach ($trustedIps as $ip) {
@ -61,6 +75,11 @@ class RouterProxyListener implements EventSubscriberInterface
}
}
// is the Request signed?
if ($this->signer->check($request->getUri())) {
return;
}
throw new AccessDeniedHttpException();
}

View File

@ -14,17 +14,19 @@ namespace Symfony\Component\HttpKernel\RenderingStrategy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\UriSigner;
/**
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HIncludeRenderingStrategy implements RenderingStrategyInterface
class HIncludeRenderingStrategy extends GeneratorAwareRenderingStrategy
{
private $templating;
private $globalDefaultTemplate;
private $signer;
public function __construct($templating, $globalDefaultTemplate = null)
public function __construct($templating, UriSigner $signer = null, $globalDefaultTemplate = null)
{
if (!$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) {
throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of \Twig_Environment or Symfony\Component\Templating\EngineInterface');
@ -32,16 +34,20 @@ class HIncludeRenderingStrategy implements RenderingStrategyInterface
$this->templating = $templating;
$this->globalDefaultTemplate = $globalDefaultTemplate;
$this->signer = $signer;
}
public function render($uri, Request $request = null, array $options = array())
{
if ($uri instanceof ControllerReference) {
// FIXME: can we sign the proxy URL instead?
throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy.');
if (null === $this->signer) {
throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.');
}
$uri = $this->signer->sign($this->generateProxyUri($uri, $request));
}
$defaultTemplate = $options['default'] ?: null;
$defaultTemplate = isset($options['default']) ? $options['default'] : null;
$defaultContent = null;
if (null !== $defaultTemplate) {

View File

@ -0,0 +1,72 @@
<?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\Component\HttpKernel;
/**
* UriSigner.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class UriSigner
{
private $secret;
/**
* Constructor.
*
* @param string $secret A secret
*/
public function __construct($secret)
{
$this->secret = $secret;
}
/**
* Signs a URI.
*
* The given URI is signed by adding a _hash query string parameter
* which value depends on the URI and the secret.
*
* @param string $uri A URI to sign
*
* @return string The signed URI
*/
public function sign($uri)
{
return $uri.(false === (strpos($uri, '?')) ? '?' : '&').'_hash='.$this->computeHash($uri);
}
/**
* Checks that a URI contains the correct hash.
*
* @param string $uri A signed URI
*
* @return Boolean True if the URI is signed correctly, false otherwise
*/
public function check($uri)
{
if (!preg_match('/(\?|&)_hash=(.+?)(&|$)/', $uri, $matches, PREG_OFFSET_CAPTURE)) {
return false;
}
// the naked URI is the URI without the _hash parameter (we need to keep the ? if there is some other parameters after)
$offset = ('?' == $matches[1][0] && '&' != $matches[3][0]) ? 0 : 1;
$nakedUri = substr($uri, 0, $matches[0][1] + $offset).substr($uri, $matches[0][1] + strlen($matches[0][0]));
return $this->computeHash($nakedUri) === $matches[2][0];
}
private function computeHash($uri)
{
return urlencode(base64_encode(hash_hmac('sha1', $uri, $this->secret, true)));
}
}