Merge branch '2.8' into 3.3
* 2.8: fixed CS fixed CS [Security] Namespace generated CSRF tokens depending of the current scheme ensure that submitted data are uploaded files [Console] remove dead code bumped Symfony version to 2.8.31 updated VERSION for 2.8.30 updated CHANGELOG for 2.8.30 bumped Symfony version to 2.7.38 updated VERSION for 2.7.37 updated CHANGELOG for 2.7.37 [Security] Validate redirect targets using the session cookie domain prevent bundle readers from breaking out of paths
This commit is contained in:
commit
ea2447f0b8
@ -18,6 +18,7 @@
|
||||
<service id="security.csrf.token_manager" class="Symfony\Component\Security\Csrf\CsrfTokenManager" public="true">
|
||||
<argument type="service" id="security.csrf.token_generator" />
|
||||
<argument type="service" id="security.csrf.token_storage" />
|
||||
<argument type="service" id="request_stack" on-invalid="ignore" />
|
||||
</service>
|
||||
<service id="Symfony\Component\Security\Csrf\CsrfTokenManagerInterface" alias="security.csrf.token_manager" />
|
||||
</services>
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?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\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Uses the session domain to restrict allowed redirection targets.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class AddSessionDomainConstraintPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionOptions = $container->getParameter('session.storage.options');
|
||||
$domainRegexp = empty($sessionOptions['cookie_domain']) ? '%s' : sprintf('(?:%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.')));
|
||||
$domainRegexp = (empty($sessionOptions['cookie_secure']) ? 'https?://' : 'https://').$domainRegexp;
|
||||
|
||||
$container->findDefinition('security.http_utils')->addArgument(sprintf('{^%s$}i', $domainRegexp));
|
||||
}
|
||||
}
|
@ -13,8 +13,10 @@ namespace Symfony\Bundle\SecurityBundle;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory;
|
||||
@ -57,5 +59,6 @@ class SecurityBundle extends Bundle
|
||||
$extension->addUserProviderFactory(new InMemoryFactory());
|
||||
$extension->addUserProviderFactory(new LdapFactory());
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,131 @@
|
||||
<?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\Bundle\SecurityBundle\Tests\DependencyInjection\Compiler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class AddSessionDomainConstraintPassTest extends TestCase
|
||||
{
|
||||
public function testSessionCookie()
|
||||
{
|
||||
$container = $this->createContainer(array('cookie_domain' => '.symfony.com.', 'cookie_secure' => true));
|
||||
|
||||
$utils = $container->get('security.http_utils');
|
||||
$request = Request::create('/', 'get');
|
||||
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://symfony.com/blog')->isRedirect('https://symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.symfony.com/blog')->isRedirect('https://www.symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://localhost/foo')->isRedirect('https://localhost/foo'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.localhost/foo')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://symfony.com/blog')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://pirate.com/foo')->isRedirect('http://localhost/'));
|
||||
}
|
||||
|
||||
public function testSessionNoDomain()
|
||||
{
|
||||
$container = $this->createContainer(array('cookie_secure' => true));
|
||||
|
||||
$utils = $container->get('security.http_utils');
|
||||
$request = Request::create('/', 'get');
|
||||
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://symfony.com/blog')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.symfony.com/blog')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://localhost/foo')->isRedirect('https://localhost/foo'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.localhost/foo')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://symfony.com/blog')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://pirate.com/foo')->isRedirect('http://localhost/'));
|
||||
}
|
||||
|
||||
public function testSessionNoSecure()
|
||||
{
|
||||
$container = $this->createContainer(array('cookie_domain' => '.symfony.com.'));
|
||||
|
||||
$utils = $container->get('security.http_utils');
|
||||
$request = Request::create('/', 'get');
|
||||
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://symfony.com/blog')->isRedirect('https://symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.symfony.com/blog')->isRedirect('https://www.symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://localhost/foo')->isRedirect('https://localhost/foo'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.localhost/foo')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://symfony.com/blog')->isRedirect('http://symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://pirate.com/foo')->isRedirect('http://localhost/'));
|
||||
}
|
||||
|
||||
public function testSessionNoSecureAndNoDomain()
|
||||
{
|
||||
$container = $this->createContainer(array());
|
||||
|
||||
$utils = $container->get('security.http_utils');
|
||||
$request = Request::create('/', 'get');
|
||||
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://symfony.com/blog')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.symfony.com/blog')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://localhost/foo')->isRedirect('https://localhost/foo'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://localhost/foo')->isRedirect('http://localhost/foo'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.localhost/foo')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://symfony.com/blog')->isRedirect('http://localhost/'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://pirate.com/foo')->isRedirect('http://localhost/'));
|
||||
}
|
||||
|
||||
public function testNoSession()
|
||||
{
|
||||
$container = $this->createContainer(null);
|
||||
|
||||
$utils = $container->get('security.http_utils');
|
||||
$request = Request::create('/', 'get');
|
||||
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://symfony.com/blog')->isRedirect('https://symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.symfony.com/blog')->isRedirect('https://www.symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://localhost/foo')->isRedirect('https://localhost/foo'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'https://www.localhost/foo')->isRedirect('https://www.localhost/foo'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://symfony.com/blog')->isRedirect('http://symfony.com/blog'));
|
||||
$this->assertTrue($utils->createRedirectResponse($request, 'http://pirate.com/foo')->isRedirect('http://pirate.com/foo'));
|
||||
}
|
||||
|
||||
private function createContainer($sessionStorageOptions)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->setParameter('kernel.cache_dir', __DIR__);
|
||||
$container->setParameter('kernel.charset', 'UTF-8');
|
||||
$container->setParameter('kernel.container_class', 'cc');
|
||||
$container->setParameter('kernel.debug', true);
|
||||
$container->setParameter('kernel.root_dir', __DIR__);
|
||||
$container->setParameter('kernel.secret', __DIR__);
|
||||
if (null !== $sessionStorageOptions) {
|
||||
$container->setParameter('session.storage.options', $sessionStorageOptions);
|
||||
}
|
||||
$container->setParameter('request_listener.http_port', 80);
|
||||
$container->setParameter('request_listener.https_port', 443);
|
||||
|
||||
$config = array(
|
||||
'security' => array(
|
||||
'providers' => array('some_provider' => array('id' => 'foo')),
|
||||
'firewalls' => array('some_firewall' => array('security' => false)),
|
||||
),
|
||||
);
|
||||
|
||||
$ext = new FrameworkExtension();
|
||||
$ext->load(array(), $container);
|
||||
|
||||
$ext = new SecurityExtension();
|
||||
$ext->load($config, $container);
|
||||
|
||||
(new AddSessionDomainConstraintPass())->process($container);
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
@ -120,15 +120,13 @@ class Application
|
||||
try {
|
||||
$e = null;
|
||||
$exitCode = $this->doRun($input, $output);
|
||||
} catch (\Exception $x) {
|
||||
$e = $x;
|
||||
} catch (\Throwable $x) {
|
||||
$e = new FatalThrowableError($x);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
if (null !== $e) {
|
||||
if (!$this->catchExceptions || !$x instanceof \Exception) {
|
||||
throw $x;
|
||||
if (!$this->catchExceptions || !$e instanceof \Exception) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
|
@ -48,6 +48,11 @@ CHANGELOG
|
||||
* moved data trimming logic of TrimListener into StringUtil
|
||||
* [BC BREAK] When registering a type extension through the DI extension, the tag alias has to match the actual extended type.
|
||||
|
||||
2.7.38
|
||||
------
|
||||
|
||||
* [BC BREAK] the `isFileUpload()` method was added to the `RequestHandlerInterface`
|
||||
|
||||
2.7.0
|
||||
-----
|
||||
|
||||
|
@ -27,20 +27,35 @@ class FileType extends AbstractType
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
if ($options['multiple']) {
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
|
||||
$form = $event->getForm();
|
||||
$data = $event->getData();
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
|
||||
$form = $event->getForm();
|
||||
$requestHandler = $form->getConfig()->getRequestHandler();
|
||||
$data = null;
|
||||
|
||||
if ($options['multiple']) {
|
||||
$data = array();
|
||||
|
||||
foreach ($event->getData() as $file) {
|
||||
if ($requestHandler->isFileUpload($file)) {
|
||||
$data[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
// submitted data for an input file (not required) without choosing any file
|
||||
if (array(null) === $data) {
|
||||
if (array(null) === $data || array() === $data) {
|
||||
$emptyData = $form->getConfig()->getEmptyData();
|
||||
|
||||
$data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData;
|
||||
$event->setData($data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$event->setData($data);
|
||||
} elseif (!$requestHandler->isFileUpload($event->getData())) {
|
||||
$emptyData = $form->getConfig()->getEmptyData();
|
||||
|
||||
$data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData;
|
||||
$event->setData($data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@ use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\RequestHandlerInterface;
|
||||
use Symfony\Component\Form\Util\ServerParams;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
@ -28,9 +29,6 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface
|
||||
{
|
||||
private $serverParams;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ServerParams $serverParams = null)
|
||||
{
|
||||
$this->serverParams = $serverParams ?: new ServerParams();
|
||||
@ -109,4 +107,9 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface
|
||||
|
||||
$form->submit($data, 'PATCH' !== $method);
|
||||
}
|
||||
|
||||
public function isFileUpload($data)
|
||||
{
|
||||
return $data instanceof File;
|
||||
}
|
||||
}
|
||||
|
@ -23,14 +23,6 @@ class NativeRequestHandler implements RequestHandlerInterface
|
||||
{
|
||||
private $serverParams;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ServerParams $params = null)
|
||||
{
|
||||
$this->serverParams = $params ?: new ServerParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* The allowed keys of the $_FILES array.
|
||||
*/
|
||||
@ -42,6 +34,11 @@ class NativeRequestHandler implements RequestHandlerInterface
|
||||
'type',
|
||||
);
|
||||
|
||||
public function __construct(ServerParams $params = null)
|
||||
{
|
||||
$this->serverParams = $params ?: new ServerParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -121,6 +118,14 @@ class NativeRequestHandler implements RequestHandlerInterface
|
||||
$form->submit($data, 'PATCH' !== $method);
|
||||
}
|
||||
|
||||
public function isFileUpload($data)
|
||||
{
|
||||
// POST data will always be strings or arrays of strings. Thus, we can be sure
|
||||
// that the submitted data is a file upload if the "error" value is an integer
|
||||
// (this value must have been injected by PHP itself).
|
||||
return is_array($data) && isset($data['error']) && is_int($data['error']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method used to submit the request to the server.
|
||||
*
|
||||
|
@ -25,4 +25,11 @@ interface RequestHandlerInterface
|
||||
* @param mixed $request The current request
|
||||
*/
|
||||
public function handleRequest(FormInterface $form, $request = null);
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFileUpload($data);
|
||||
}
|
||||
|
@ -353,12 +353,24 @@ abstract class AbstractRequestHandlerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testUploadedFilesAreAccepted()
|
||||
{
|
||||
$this->assertTrue($this->requestHandler->isFileUpload($this->getMockFile()));
|
||||
}
|
||||
|
||||
public function testInvalidFilesAreRejected()
|
||||
{
|
||||
$this->assertFalse($this->requestHandler->isFileUpload($this->getInvalidFile()));
|
||||
}
|
||||
|
||||
abstract protected function setRequestData($method, $data, $files = array());
|
||||
|
||||
abstract protected function getRequestHandler();
|
||||
|
||||
abstract protected function getMockFile($suffix = '');
|
||||
|
||||
abstract protected function getInvalidFile();
|
||||
|
||||
protected function getMockForm($name, $method = null, $compound = true)
|
||||
{
|
||||
$config = $this->getMockBuilder('Symfony\Component\Form\FormConfigInterface')->getMock();
|
||||
|
@ -11,6 +11,11 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler;
|
||||
use Symfony\Component\Form\NativeRequestHandler;
|
||||
use Symfony\Component\Form\RequestHandlerInterface;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class FileTypeTest extends BaseTypeTest
|
||||
{
|
||||
const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\FileType';
|
||||
@ -29,40 +34,49 @@ class FileTypeTest extends BaseTypeTest
|
||||
$this->assertSame($data, $form->getData());
|
||||
}
|
||||
|
||||
public function testSubmit()
|
||||
/**
|
||||
* @dataProvider requestHandlerProvider
|
||||
*/
|
||||
public function testSubmit(RequestHandlerInterface $requestHandler)
|
||||
{
|
||||
$form = $this->factory->createBuilder(static::TESTED_TYPE)->getForm();
|
||||
$data = $this->createUploadedFileMock('abcdef', 'original.jpg', true);
|
||||
$form = $this->factory->createBuilder(static::TESTED_TYPE)->setRequestHandler($requestHandler)->getForm();
|
||||
$data = $this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg');
|
||||
|
||||
$form->submit($data);
|
||||
|
||||
$this->assertSame($data, $form->getData());
|
||||
}
|
||||
|
||||
public function testSetDataMultiple()
|
||||
/**
|
||||
* @dataProvider requestHandlerProvider
|
||||
*/
|
||||
public function testSetDataMultiple(RequestHandlerInterface $requestHandler)
|
||||
{
|
||||
$form = $this->factory->createBuilder(static::TESTED_TYPE, null, array(
|
||||
'multiple' => true,
|
||||
))->getForm();
|
||||
))->setRequestHandler($requestHandler)->getForm();
|
||||
|
||||
$data = array(
|
||||
$this->createUploadedFileMock('abcdef', 'first.jpg', true),
|
||||
$this->createUploadedFileMock('zyxwvu', 'second.jpg', true),
|
||||
$this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg'),
|
||||
$this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo2', 'foo2.jpg'),
|
||||
);
|
||||
|
||||
$form->setData($data);
|
||||
$this->assertSame($data, $form->getData());
|
||||
}
|
||||
|
||||
public function testSubmitMultiple()
|
||||
/**
|
||||
* @dataProvider requestHandlerProvider
|
||||
*/
|
||||
public function testSubmitMultiple(RequestHandlerInterface $requestHandler)
|
||||
{
|
||||
$form = $this->factory->createBuilder(static::TESTED_TYPE, null, array(
|
||||
'multiple' => true,
|
||||
))->getForm();
|
||||
))->setRequestHandler($requestHandler)->getForm();
|
||||
|
||||
$data = array(
|
||||
$this->createUploadedFileMock('abcdef', 'first.jpg', true),
|
||||
$this->createUploadedFileMock('zyxwvu', 'second.jpg', true),
|
||||
$this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg'),
|
||||
$this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo2', 'foo2.jpg'),
|
||||
);
|
||||
|
||||
$form->submit($data);
|
||||
@ -73,11 +87,14 @@ class FileTypeTest extends BaseTypeTest
|
||||
$this->assertArrayHasKey('multiple', $view->vars['attr']);
|
||||
}
|
||||
|
||||
public function testDontPassValueToView()
|
||||
/**
|
||||
* @dataProvider requestHandlerProvider
|
||||
*/
|
||||
public function testDontPassValueToView(RequestHandlerInterface $requestHandler)
|
||||
{
|
||||
$form = $this->factory->create(static::TESTED_TYPE);
|
||||
$form = $this->factory->createBuilder(static::TESTED_TYPE)->setRequestHandler($requestHandler)->getForm();
|
||||
$form->submit(array(
|
||||
'file' => $this->createUploadedFileMock('abcdef', 'original.jpg', true),
|
||||
'file' => $this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg'),
|
||||
));
|
||||
|
||||
$this->assertEquals('', $form->createView()->vars['value']);
|
||||
@ -109,29 +126,59 @@ class FileTypeTest extends BaseTypeTest
|
||||
$this->assertSame(array(), $form->getViewData());
|
||||
}
|
||||
|
||||
private function createUploadedFileMock($name, $originalName, $valid)
|
||||
/**
|
||||
* @dataProvider requestHandlerProvider
|
||||
*/
|
||||
public function testSubmittedFilePathsAreDropped(RequestHandlerInterface $requestHandler)
|
||||
{
|
||||
$file = $this
|
||||
->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile')
|
||||
->setConstructorArgs(array(__DIR__.'/../../../Fixtures/foo', 'foo'))
|
||||
->getMock()
|
||||
;
|
||||
$file
|
||||
->expects($this->any())
|
||||
->method('getBasename')
|
||||
->will($this->returnValue($name))
|
||||
;
|
||||
$file
|
||||
->expects($this->any())
|
||||
->method('getClientOriginalName')
|
||||
->will($this->returnValue($originalName))
|
||||
;
|
||||
$file
|
||||
->expects($this->any())
|
||||
->method('isValid')
|
||||
->will($this->returnValue($valid))
|
||||
;
|
||||
$form = $this->factory->createBuilder(static::TESTED_TYPE)->setRequestHandler($requestHandler)->getForm();
|
||||
$form->submit('file:///etc/passwd');
|
||||
|
||||
return $file;
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertNull($form->getNormData());
|
||||
$this->assertSame('', $form->getViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider requestHandlerProvider
|
||||
*/
|
||||
public function testMultipleSubmittedFilePathsAreDropped(RequestHandlerInterface $requestHandler)
|
||||
{
|
||||
$form = $this->factory
|
||||
->createBuilder(static::TESTED_TYPE, null, array(
|
||||
'multiple' => true,
|
||||
))
|
||||
->setRequestHandler($requestHandler)
|
||||
->getForm();
|
||||
$form->submit(array(
|
||||
'file:///etc/passwd',
|
||||
$this->createUploadedFileMock(new HttpFoundationRequestHandler(), __DIR__.'/../../../Fixtures/foo', 'foo.jpg'),
|
||||
$this->createUploadedFileMock(new NativeRequestHandler(), __DIR__.'/../../../Fixtures/foo2', 'foo2.jpg'),
|
||||
));
|
||||
|
||||
$this->assertCount(1, $form->getData());
|
||||
}
|
||||
|
||||
public function requestHandlerProvider()
|
||||
{
|
||||
return array(
|
||||
array(new HttpFoundationRequestHandler()),
|
||||
array(new NativeRequestHandler()),
|
||||
);
|
||||
}
|
||||
|
||||
private function createUploadedFileMock(RequestHandlerInterface $requestHandler, $path, $originalName)
|
||||
{
|
||||
if ($requestHandler instanceof HttpFoundationRequestHandler) {
|
||||
return new UploadedFile($path, $originalName, null, 10, null, true);
|
||||
}
|
||||
|
||||
return array(
|
||||
'name' => $originalName,
|
||||
'error' => 0,
|
||||
'type' => 'text/plain',
|
||||
'tmp_name' => $path,
|
||||
'size' => 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -51,4 +51,9 @@ class HttpFoundationRequestHandlerTest extends AbstractRequestHandlerTest
|
||||
{
|
||||
return new UploadedFile(__DIR__.'/../../Fixtures/foo'.$suffix, 'foo'.$suffix);
|
||||
}
|
||||
|
||||
protected function getInvalidFile()
|
||||
{
|
||||
return 'file:///etc/passwd';
|
||||
}
|
||||
}
|
||||
|
@ -216,4 +216,15 @@ class NativeRequestHandlerTest extends AbstractRequestHandlerTest
|
||||
'size' => 100,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getInvalidFile()
|
||||
{
|
||||
return array(
|
||||
'name' => 'upload.txt',
|
||||
'type' => 'text/plain',
|
||||
'tmp_name' => 'owfdskjasdfsa',
|
||||
'error' => '0',
|
||||
'size' => '100',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,11 @@ class JsonBundleReader implements BundleReaderInterface
|
||||
{
|
||||
$fileName = $path.'/'.$locale.'.json';
|
||||
|
||||
// prevent directory traversal attacks
|
||||
if (dirname($fileName) !== $path) {
|
||||
throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName));
|
||||
}
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new ResourceBundleNotFoundException(sprintf(
|
||||
'The resource bundle "%s" does not exist.',
|
||||
|
@ -30,6 +30,11 @@ class PhpBundleReader implements BundleReaderInterface
|
||||
{
|
||||
$fileName = $path.'/'.$locale.'.php';
|
||||
|
||||
// prevent directory traversal attacks
|
||||
if (dirname($fileName) !== $path) {
|
||||
throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName));
|
||||
}
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new ResourceBundleNotFoundException(sprintf(
|
||||
'The resource bundle "%s/%s.php" does not exist.',
|
||||
|
@ -0,0 +1 @@
|
||||
{"Foo":"Bar"}
|
@ -0,0 +1,14 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
return array(
|
||||
'Foo' => 'Bar',
|
||||
);
|
@ -69,4 +69,12 @@ class JsonBundleReaderTest extends TestCase
|
||||
{
|
||||
$this->reader->read(__DIR__.'/Fixtures/json', 'en_Invalid');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException
|
||||
*/
|
||||
public function testReaderDoesNotBreakOutOfGivenPath()
|
||||
{
|
||||
$this->reader->read(__DIR__.'/Fixtures/json', '../invalid_directory/en');
|
||||
}
|
||||
}
|
||||
|
@ -61,4 +61,12 @@ class PhpBundleReaderTest extends TestCase
|
||||
{
|
||||
$this->reader->read(__DIR__.'/Fixtures/NotAFile', 'en');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException
|
||||
*/
|
||||
public function testReaderDoesNotBreakOutOfGivenPath()
|
||||
{
|
||||
$this->reader->read(__DIR__.'/Fixtures/php', '../invalid_directory/en');
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Security\Csrf;
|
||||
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
|
||||
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
|
||||
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
|
||||
@ -20,16 +22,45 @@ use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
|
||||
* Default implementation of {@link CsrfTokenManagerInterface}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class CsrfTokenManager implements CsrfTokenManagerInterface
|
||||
{
|
||||
private $generator;
|
||||
private $storage;
|
||||
private $namespace;
|
||||
|
||||
public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null)
|
||||
/**
|
||||
* @param null|string|RequestStack|callable $namespace
|
||||
* * null: generates a namespace using $_SERVER['HTTPS']
|
||||
* * string: uses the given string
|
||||
* * RequestStack: generates a namespace using the current master request
|
||||
* * callable: uses the result of this callable (must return a string)
|
||||
*/
|
||||
public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null, $namespace = null)
|
||||
{
|
||||
$this->generator = $generator ?: new UriSafeTokenGenerator();
|
||||
$this->storage = $storage ?: new NativeSessionTokenStorage();
|
||||
|
||||
$superGlobalNamespaceGenerator = function () {
|
||||
return !empty($_SERVER['HTTPS']) && 'off' !== strtolower($_SERVER['HTTPS']) ? 'https-' : '';
|
||||
};
|
||||
|
||||
if (null === $namespace) {
|
||||
$this->namespace = $superGlobalNamespaceGenerator;
|
||||
} elseif ($namespace instanceof RequestStack) {
|
||||
$this->namespace = function () use ($namespace, $superGlobalNamespaceGenerator) {
|
||||
if ($request = $namespace->getMasterRequest()) {
|
||||
return $request->isSecure() ? 'https-' : '';
|
||||
}
|
||||
|
||||
return $superGlobalNamespaceGenerator();
|
||||
};
|
||||
} elseif (is_callable($namespace) || is_string($namespace)) {
|
||||
$this->namespace = $namespace;
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('$namespace must be a string, a callable returning a string, null or an instance of "RequestStack". "%s" given.', gettype($namespace)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,12 +68,13 @@ class CsrfTokenManager implements CsrfTokenManagerInterface
|
||||
*/
|
||||
public function getToken($tokenId)
|
||||
{
|
||||
if ($this->storage->hasToken($tokenId)) {
|
||||
$value = $this->storage->getToken($tokenId);
|
||||
$namespacedId = $this->getNamespace().$tokenId;
|
||||
if ($this->storage->hasToken($namespacedId)) {
|
||||
$value = $this->storage->getToken($namespacedId);
|
||||
} else {
|
||||
$value = $this->generator->generateToken();
|
||||
|
||||
$this->storage->setToken($tokenId, $value);
|
||||
$this->storage->setToken($namespacedId, $value);
|
||||
}
|
||||
|
||||
return new CsrfToken($tokenId, $value);
|
||||
@ -53,9 +85,10 @@ class CsrfTokenManager implements CsrfTokenManagerInterface
|
||||
*/
|
||||
public function refreshToken($tokenId)
|
||||
{
|
||||
$namespacedId = $this->getNamespace().$tokenId;
|
||||
$value = $this->generator->generateToken();
|
||||
|
||||
$this->storage->setToken($tokenId, $value);
|
||||
$this->storage->setToken($namespacedId, $value);
|
||||
|
||||
return new CsrfToken($tokenId, $value);
|
||||
}
|
||||
@ -65,7 +98,7 @@ class CsrfTokenManager implements CsrfTokenManagerInterface
|
||||
*/
|
||||
public function removeToken($tokenId)
|
||||
{
|
||||
return $this->storage->removeToken($tokenId);
|
||||
return $this->storage->removeToken($this->getNamespace().$tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,10 +106,16 @@ class CsrfTokenManager implements CsrfTokenManagerInterface
|
||||
*/
|
||||
public function isTokenValid(CsrfToken $token)
|
||||
{
|
||||
if (!$this->storage->hasToken($token->getId())) {
|
||||
$namespacedId = $this->getNamespace().$token->getId();
|
||||
if (!$this->storage->hasToken($namespacedId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_equals($this->storage->getToken($token->getId()), $token->getValue());
|
||||
return hash_equals($this->storage->getToken($namespacedId), $token->getValue());
|
||||
}
|
||||
|
||||
private function getNamespace()
|
||||
{
|
||||
return is_callable($ns = $this->namespace) ? $ns() : $ns;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
namespace Symfony\Component\Security\Csrf\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||
use Symfony\Component\Security\Csrf\CsrfTokenManager;
|
||||
|
||||
@ -21,145 +23,202 @@ use Symfony\Component\Security\Csrf\CsrfTokenManager;
|
||||
class CsrfTokenManagerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
* @dataProvider getManagerGeneratorAndStorage
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $storage;
|
||||
|
||||
/**
|
||||
* @var CsrfTokenManager
|
||||
*/
|
||||
private $manager;
|
||||
|
||||
protected function setUp()
|
||||
public function testGetNonExistingToken($namespace, $manager, $storage, $generator)
|
||||
{
|
||||
$this->generator = $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface')->getMock();
|
||||
$this->storage = $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface')->getMock();
|
||||
$this->manager = new CsrfTokenManager($this->generator, $this->storage);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->generator = null;
|
||||
$this->storage = null;
|
||||
$this->manager = null;
|
||||
}
|
||||
|
||||
public function testGetNonExistingToken()
|
||||
{
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('hasToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->generator->expects($this->once())
|
||||
$generator->expects($this->once())
|
||||
->method('generateToken')
|
||||
->will($this->returnValue('TOKEN'));
|
||||
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('setToken')
|
||||
->with('token_id', 'TOKEN');
|
||||
->with($namespace.'token_id', 'TOKEN');
|
||||
|
||||
$token = $this->manager->getToken('token_id');
|
||||
$token = $manager->getToken('token_id');
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
|
||||
$this->assertSame('token_id', $token->getId());
|
||||
$this->assertSame('TOKEN', $token->getValue());
|
||||
}
|
||||
|
||||
public function testUseExistingTokenIfAvailable()
|
||||
/**
|
||||
* @dataProvider getManagerGeneratorAndStorage
|
||||
*/
|
||||
public function testUseExistingTokenIfAvailable($namespace, $manager, $storage)
|
||||
{
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('hasToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('getToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue('TOKEN'));
|
||||
|
||||
$token = $this->manager->getToken('token_id');
|
||||
$token = $manager->getToken('token_id');
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
|
||||
$this->assertSame('token_id', $token->getId());
|
||||
$this->assertSame('TOKEN', $token->getValue());
|
||||
}
|
||||
|
||||
public function testRefreshTokenAlwaysReturnsNewToken()
|
||||
/**
|
||||
* @dataProvider getManagerGeneratorAndStorage
|
||||
*/
|
||||
public function testRefreshTokenAlwaysReturnsNewToken($namespace, $manager, $storage, $generator)
|
||||
{
|
||||
$this->storage->expects($this->never())
|
||||
$storage->expects($this->never())
|
||||
->method('hasToken');
|
||||
|
||||
$this->generator->expects($this->once())
|
||||
$generator->expects($this->once())
|
||||
->method('generateToken')
|
||||
->will($this->returnValue('TOKEN'));
|
||||
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('setToken')
|
||||
->with('token_id', 'TOKEN');
|
||||
->with($namespace.'token_id', 'TOKEN');
|
||||
|
||||
$token = $this->manager->refreshToken('token_id');
|
||||
$token = $manager->refreshToken('token_id');
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
|
||||
$this->assertSame('token_id', $token->getId());
|
||||
$this->assertSame('TOKEN', $token->getValue());
|
||||
}
|
||||
|
||||
public function testMatchingTokenIsValid()
|
||||
/**
|
||||
* @dataProvider getManagerGeneratorAndStorage
|
||||
*/
|
||||
public function testMatchingTokenIsValid($namespace, $manager, $storage)
|
||||
{
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('hasToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('getToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue('TOKEN'));
|
||||
|
||||
$this->assertTrue($this->manager->isTokenValid(new CsrfToken('token_id', 'TOKEN')));
|
||||
$this->assertTrue($manager->isTokenValid(new CsrfToken('token_id', 'TOKEN')));
|
||||
}
|
||||
|
||||
public function testNonMatchingTokenIsNotValid()
|
||||
/**
|
||||
* @dataProvider getManagerGeneratorAndStorage
|
||||
*/
|
||||
public function testNonMatchingTokenIsNotValid($namespace, $manager, $storage)
|
||||
{
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('hasToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('getToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue('TOKEN'));
|
||||
|
||||
$this->assertFalse($this->manager->isTokenValid(new CsrfToken('token_id', 'FOOBAR')));
|
||||
$this->assertFalse($manager->isTokenValid(new CsrfToken('token_id', 'FOOBAR')));
|
||||
}
|
||||
|
||||
public function testNonExistingTokenIsNotValid()
|
||||
/**
|
||||
* @dataProvider getManagerGeneratorAndStorage
|
||||
*/
|
||||
public function testNonExistingTokenIsNotValid($namespace, $manager, $storage)
|
||||
{
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('hasToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->storage->expects($this->never())
|
||||
$storage->expects($this->never())
|
||||
->method('getToken');
|
||||
|
||||
$this->assertFalse($this->manager->isTokenValid(new CsrfToken('token_id', 'FOOBAR')));
|
||||
$this->assertFalse($manager->isTokenValid(new CsrfToken('token_id', 'FOOBAR')));
|
||||
}
|
||||
|
||||
public function testRemoveToken()
|
||||
/**
|
||||
* @dataProvider getManagerGeneratorAndStorage
|
||||
*/
|
||||
public function testRemoveToken($namespace, $manager, $storage)
|
||||
{
|
||||
$this->storage->expects($this->once())
|
||||
$storage->expects($this->once())
|
||||
->method('removeToken')
|
||||
->with('token_id')
|
||||
->with($namespace.'token_id')
|
||||
->will($this->returnValue('REMOVED_TOKEN'));
|
||||
|
||||
$this->assertSame('REMOVED_TOKEN', $this->manager->removeToken('token_id'));
|
||||
$this->assertSame('REMOVED_TOKEN', $manager->removeToken('token_id'));
|
||||
}
|
||||
|
||||
public function testNamespaced()
|
||||
{
|
||||
$generator = $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface')->getMock();
|
||||
$storage = $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface')->getMock();
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push(new Request(array(), array(), array(), array(), array(), array('HTTPS' => 'on')));
|
||||
|
||||
$manager = new CsrfTokenManager($generator, $storage, null, $requestStack);
|
||||
|
||||
$token = $manager->getToken('foo');
|
||||
$this->assertSame('foo', $token->getId());
|
||||
}
|
||||
|
||||
public function getManagerGeneratorAndStorage()
|
||||
{
|
||||
$data = array();
|
||||
|
||||
list($generator, $storage) = $this->getGeneratorAndStorage();
|
||||
$data[] = array('', new CsrfTokenManager($generator, $storage, ''), $storage, $generator);
|
||||
|
||||
list($generator, $storage) = $this->getGeneratorAndStorage();
|
||||
$data[] = array('https-', new CsrfTokenManager($generator, $storage), $storage, $generator);
|
||||
|
||||
list($generator, $storage) = $this->getGeneratorAndStorage();
|
||||
$data[] = array('aNamespace-', new CsrfTokenManager($generator, $storage, 'aNamespace-'), $storage, $generator);
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push(new Request(array(), array(), array(), array(), array(), array('HTTPS' => 'on')));
|
||||
list($generator, $storage) = $this->getGeneratorAndStorage();
|
||||
$data[] = array('https-', new CsrfTokenManager($generator, $storage, $requestStack), $storage, $generator);
|
||||
|
||||
list($generator, $storage) = $this->getGeneratorAndStorage();
|
||||
$data[] = array('generated-', new CsrfTokenManager($generator, $storage, function () {
|
||||
return 'generated-';
|
||||
}), $storage, $generator);
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push(new Request());
|
||||
list($generator, $storage) = $this->getGeneratorAndStorage();
|
||||
$data[] = array('', new CsrfTokenManager($generator, $storage, $requestStack), $storage, $generator);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getGeneratorAndStorage()
|
||||
{
|
||||
return array(
|
||||
$this->getMockBuilder('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface')->getMock(),
|
||||
$this->getMockBuilder('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface')->getMock(),
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
unset($_SERVER['HTTPS']);
|
||||
}
|
||||
}
|
||||
|
@ -29,20 +29,23 @@ class HttpUtils
|
||||
{
|
||||
private $urlGenerator;
|
||||
private $urlMatcher;
|
||||
private $domainRegexp;
|
||||
|
||||
/**
|
||||
* @param UrlGeneratorInterface $urlGenerator A UrlGeneratorInterface instance
|
||||
* @param UrlMatcherInterface|RequestMatcherInterface $urlMatcher The URL or Request matcher
|
||||
* @param string|null $domainRegexp A regexp that the target of HTTP redirections must match, scheme included
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(UrlGeneratorInterface $urlGenerator = null, $urlMatcher = null)
|
||||
public function __construct(UrlGeneratorInterface $urlGenerator = null, $urlMatcher = null, $domainRegexp = null)
|
||||
{
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
if (null !== $urlMatcher && !$urlMatcher instanceof UrlMatcherInterface && !$urlMatcher instanceof RequestMatcherInterface) {
|
||||
throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.');
|
||||
}
|
||||
$this->urlMatcher = $urlMatcher;
|
||||
$this->domainRegexp = $domainRegexp;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,6 +59,10 @@ class HttpUtils
|
||||
*/
|
||||
public function createRedirectResponse(Request $request, $path, $status = 302)
|
||||
{
|
||||
if (null !== $this->domainRegexp && preg_match('#^https?://[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) {
|
||||
$path = '/';
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->generateUri($request, $path), $status);
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,38 @@ class HttpUtilsTest extends TestCase
|
||||
$this->assertTrue($response->isRedirect('http://symfony.com/'));
|
||||
}
|
||||
|
||||
public function testCreateRedirectResponseWithDomainRegexp()
|
||||
{
|
||||
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://symfony\.com$#i');
|
||||
$response = $utils->createRedirectResponse($this->getRequest(), 'http://symfony.com/blog');
|
||||
|
||||
$this->assertTrue($response->isRedirect('http://symfony.com/blog'));
|
||||
}
|
||||
|
||||
public function testCreateRedirectResponseWithRequestsDomain()
|
||||
{
|
||||
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');
|
||||
$response = $utils->createRedirectResponse($this->getRequest(), 'http://localhost/blog');
|
||||
|
||||
$this->assertTrue($response->isRedirect('http://localhost/blog'));
|
||||
}
|
||||
|
||||
public function testCreateRedirectResponseWithBadRequestsDomain()
|
||||
{
|
||||
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');
|
||||
$response = $utils->createRedirectResponse($this->getRequest(), 'http://pirate.net/foo');
|
||||
|
||||
$this->assertTrue($response->isRedirect('http://localhost/'));
|
||||
}
|
||||
|
||||
public function testCreateRedirectResponseWithProtocolRelativeTarget()
|
||||
{
|
||||
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');
|
||||
$response = $utils->createRedirectResponse($this->getRequest(), '//evil.com/do-bad-things');
|
||||
|
||||
$this->assertTrue($response->isRedirect('http://localhost//evil.com/do-bad-things'), 'Protocol-relative redirection should not be supported for security reasons');
|
||||
}
|
||||
|
||||
public function testCreateRedirectResponseWithRouteName()
|
||||
{
|
||||
$utils = new HttpUtils($urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock());
|
||||
|
Reference in New Issue
Block a user