[HttpKernel] added a URL signer mechanism for hincludes
This commit is contained in:
parent
a0c49c3a94
commit
892f00ffee
@ -36,12 +36,15 @@
|
|||||||
<service id="http_content_renderer.strategy.hinclude" class="%http_content_renderer.strategy.hinclude.class%">
|
<service id="http_content_renderer.strategy.hinclude" class="%http_content_renderer.strategy.hinclude.class%">
|
||||||
<tag name="kernel.content_renderer_strategy" />
|
<tag name="kernel.content_renderer_strategy" />
|
||||||
<argument type="service" id="templating" />
|
<argument type="service" id="templating" />
|
||||||
|
<argument type="service" id="uri_signer" />
|
||||||
<argument>%http_content_renderer.strategy.hinclude.global_template%</argument>
|
<argument>%http_content_renderer.strategy.hinclude.global_template%</argument>
|
||||||
|
<call method="setUrlGenerator"><argument type="service" id="router" /></call>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- FIXME: make the listener registration optional via a configuration setting? -->
|
<!-- 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%">
|
<service id="http_content_renderer.listener.router_proxy" class="%http_content_renderer.listener.router_proxy.class%">
|
||||||
<tag name="kernel.event_subscriber" />
|
<tag name="kernel.event_subscriber" />
|
||||||
|
<argument type="service" id="uri_signer" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
</services>
|
</services>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<parameter key="cache_warmer.class">Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate</parameter>
|
<parameter key="cache_warmer.class">Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate</parameter>
|
||||||
<parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</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="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter>
|
||||||
|
<parameter key="uri_signer.class">Symfony\Component\HttpKernel\UriSigner</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
|
|
||||||
<services>
|
<services>
|
||||||
@ -51,5 +52,9 @@
|
|||||||
<argument type="service" id="kernel" />
|
<argument type="service" id="kernel" />
|
||||||
<argument>%kernel.root_dir%/Resources</argument>
|
<argument>%kernel.root_dir%/Resources</argument>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="uri_signer" class="%uri_signer.class%">
|
||||||
|
<argument>%kernel.secret%</argument>
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\IpUtils;
|
|||||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\UriSigner;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,6 +29,13 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|||||||
*/
|
*/
|
||||||
class RouterProxyListener implements EventSubscriberInterface
|
class RouterProxyListener implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
|
private $signer;
|
||||||
|
|
||||||
|
public function __construct(UriSigner $signer)
|
||||||
|
{
|
||||||
|
$this->signer = $signer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fixes request attributes when the route is '_proxy'.
|
* Fixes request attributes when the route is '_proxy'.
|
||||||
*
|
*
|
||||||
@ -43,7 +51,7 @@ class RouterProxyListener implements EventSubscriberInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->checkRequest($request);
|
$this->validateRequest($request);
|
||||||
|
|
||||||
parse_str($request->query->get('path', ''), $attributes);
|
parse_str($request->query->get('path', ''), $attributes);
|
||||||
$request->attributes->add($attributes);
|
$request->attributes->add($attributes);
|
||||||
@ -51,8 +59,14 @@ class RouterProxyListener implements EventSubscriberInterface
|
|||||||
$request->query->remove('path');
|
$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());
|
$trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies());
|
||||||
$remoteAddress = $request->server->get('REMOTE_ADDR');
|
$remoteAddress = $request->server->get('REMOTE_ADDR');
|
||||||
foreach ($trustedIps as $ip) {
|
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();
|
throw new AccessDeniedHttpException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,17 +14,19 @@ namespace Symfony\Component\HttpKernel\RenderingStrategy;
|
|||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Templating\EngineInterface;
|
use Symfony\Component\Templating\EngineInterface;
|
||||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||||
|
use Symfony\Component\HttpKernel\UriSigner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
class HIncludeRenderingStrategy implements RenderingStrategyInterface
|
class HIncludeRenderingStrategy extends GeneratorAwareRenderingStrategy
|
||||||
{
|
{
|
||||||
private $templating;
|
private $templating;
|
||||||
private $globalDefaultTemplate;
|
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) {
|
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');
|
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->templating = $templating;
|
||||||
$this->globalDefaultTemplate = $globalDefaultTemplate;
|
$this->globalDefaultTemplate = $globalDefaultTemplate;
|
||||||
|
$this->signer = $signer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render($uri, Request $request = null, array $options = array())
|
public function render($uri, Request $request = null, array $options = array())
|
||||||
{
|
{
|
||||||
if ($uri instanceof ControllerReference) {
|
if ($uri instanceof ControllerReference) {
|
||||||
// FIXME: can we sign the proxy URL instead?
|
if (null === $this->signer) {
|
||||||
throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy.');
|
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;
|
$defaultContent = null;
|
||||||
|
|
||||||
if (null !== $defaultTemplate) {
|
if (null !== $defaultTemplate) {
|
||||||
|
72
src/Symfony/Component/HttpKernel/UriSigner.php
Normal file
72
src/Symfony/Component/HttpKernel/UriSigner.php
Normal 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)));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user