Merge branch '3.3' into 3.4

* 3.3:
  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 3.3.13
  updated VERSION for 3.3.12
  updated CHANGELOG for 3.3.12
  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:
Nicolas Grekas 2017-11-16 17:25:26 +02:00
commit caa10ae038
25 changed files with 603 additions and 137 deletions

View File

@ -7,6 +7,12 @@ in 3.3 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.3.0...v3.3.1 To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.3.0...v3.3.1
* 3.3.12 (2017-11-13)
* bug #24954 [DI] Fix dumping with custom base class (nicolas-grekas)
* bug #24952 [HttpFoundation] Fix session-related BC break (nicolas-grekas, sroze)
* bug #24929 [Console] Fix traversable autocomplete values (ro0NL)
* 3.3.11 (2017-11-10) * 3.3.11 (2017-11-10)
* bug #24888 [FrameworkBundle] Specifically inject the debug dispatcher in the collector (ogizanagi) * bug #24888 [FrameworkBundle] Specifically inject the debug dispatcher in the collector (ogizanagi)

View File

@ -18,6 +18,7 @@
<service id="security.csrf.token_manager" class="Symfony\Component\Security\Csrf\CsrfTokenManager" public="true"> <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_generator" />
<argument type="service" id="security.csrf.token_storage" /> <argument type="service" id="security.csrf.token_storage" />
<argument type="service" id="request_stack" on-invalid="ignore" />
</service> </service>
<service id="Symfony\Component\Security\Csrf\CsrfTokenManagerInterface" alias="security.csrf.token_manager" /> <service id="Symfony\Component\Security\Csrf\CsrfTokenManagerInterface" alias="security.csrf.token_manager" />
</services> </services>

View File

@ -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));
}
}

View File

@ -14,8 +14,10 @@ namespace Symfony\Bundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; 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\FormLoginFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory;
@ -58,6 +60,7 @@ class SecurityBundle extends Bundle
$extension->addUserProviderFactory(new InMemoryFactory()); $extension->addUserProviderFactory(new InMemoryFactory());
$extension->addUserProviderFactory(new LdapFactory()); $extension->addUserProviderFactory(new LdapFactory());
$container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddSecurityVotersPass());
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_AFTER_REMOVING);
} }
public function registerCommands(Application $application) public function registerCommands(Application $application)

View File

@ -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;
}
}

View File

@ -127,15 +127,13 @@ class Application
try { try {
$e = null; $e = null;
$exitCode = $this->doRun($input, $output); $exitCode = $this->doRun($input, $output);
} catch (\Exception $x) { } catch (\Exception $e) {
$e = $x; } catch (\Throwable $e) {
} catch (\Throwable $x) {
$e = new FatalThrowableError($x);
} }
if (null !== $e) { if (null !== $e) {
if (!$this->catchExceptions || !$x instanceof \Exception) { if (!$this->catchExceptions || !$e instanceof \Exception) {
throw $x; throw $e;
} }
if ($output instanceof ConsoleOutputInterface) { if ($output instanceof ConsoleOutputInterface) {

View File

@ -57,6 +57,11 @@ CHANGELOG
* moved data trimming logic of TrimListener into StringUtil * 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. * [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 2.7.0
----- -----

View File

@ -27,20 +27,35 @@ class FileType extends AbstractType
*/ */
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
if ($options['multiple']) { $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { $form = $event->getForm();
$form = $event->getForm(); $requestHandler = $form->getConfig()->getRequestHandler();
$data = $event->getData(); $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 // 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(); $emptyData = $form->getConfig()->getEmptyData();
$data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData; $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);
}
});
} }
/** /**

View File

@ -16,6 +16,7 @@ use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\RequestHandlerInterface; use Symfony\Component\Form\RequestHandlerInterface;
use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\Form\Util\ServerParams;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
@ -28,9 +29,6 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface
{ {
private $serverParams; private $serverParams;
/**
* {@inheritdoc}
*/
public function __construct(ServerParams $serverParams = null) public function __construct(ServerParams $serverParams = null)
{ {
$this->serverParams = $serverParams ?: new ServerParams(); $this->serverParams = $serverParams ?: new ServerParams();
@ -109,4 +107,9 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface
$form->submit($data, 'PATCH' !== $method); $form->submit($data, 'PATCH' !== $method);
} }
public function isFileUpload($data)
{
return $data instanceof File;
}
} }

View File

@ -23,14 +23,6 @@ class NativeRequestHandler implements RequestHandlerInterface
{ {
private $serverParams; private $serverParams;
/**
* {@inheritdoc}
*/
public function __construct(ServerParams $params = null)
{
$this->serverParams = $params ?: new ServerParams();
}
/** /**
* The allowed keys of the $_FILES array. * The allowed keys of the $_FILES array.
*/ */
@ -42,6 +34,11 @@ class NativeRequestHandler implements RequestHandlerInterface
'type', 'type',
); );
public function __construct(ServerParams $params = null)
{
$this->serverParams = $params ?: new ServerParams();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -121,6 +118,14 @@ class NativeRequestHandler implements RequestHandlerInterface
$form->submit($data, 'PATCH' !== $method); $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. * Returns the method used to submit the request to the server.
* *

View File

@ -25,4 +25,11 @@ interface RequestHandlerInterface
* @param mixed $request The current request * @param mixed $request The current request
*/ */
public function handleRequest(FormInterface $form, $request = null); public function handleRequest(FormInterface $form, $request = null);
/**
* @param mixed $data
*
* @return bool
*/
public function isFileUpload($data);
} }

View File

@ -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 setRequestData($method, $data, $files = array());
abstract protected function getRequestHandler(); abstract protected function getRequestHandler();
abstract protected function getMockFile($suffix = ''); abstract protected function getMockFile($suffix = '');
abstract protected function getInvalidFile();
protected function getMockForm($name, $method = null, $compound = true) protected function getMockForm($name, $method = null, $compound = true)
{ {
$config = $this->getMockBuilder('Symfony\Component\Form\FormConfigInterface')->getMock(); $config = $this->getMockBuilder('Symfony\Component\Form\FormConfigInterface')->getMock();

View File

@ -11,6 +11,11 @@
namespace Symfony\Component\Form\Tests\Extension\Core\Type; 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 class FileTypeTest extends BaseTypeTest
{ {
const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\FileType'; const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\FileType';
@ -29,40 +34,49 @@ class FileTypeTest extends BaseTypeTest
$this->assertSame($data, $form->getData()); $this->assertSame($data, $form->getData());
} }
public function testSubmit() /**
* @dataProvider requestHandlerProvider
*/
public function testSubmit(RequestHandlerInterface $requestHandler)
{ {
$form = $this->factory->createBuilder(static::TESTED_TYPE)->getForm(); $form = $this->factory->createBuilder(static::TESTED_TYPE)->setRequestHandler($requestHandler)->getForm();
$data = $this->createUploadedFileMock('abcdef', 'original.jpg', true); $data = $this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg');
$form->submit($data); $form->submit($data);
$this->assertSame($data, $form->getData()); $this->assertSame($data, $form->getData());
} }
public function testSetDataMultiple() /**
* @dataProvider requestHandlerProvider
*/
public function testSetDataMultiple(RequestHandlerInterface $requestHandler)
{ {
$form = $this->factory->createBuilder(static::TESTED_TYPE, null, array( $form = $this->factory->createBuilder(static::TESTED_TYPE, null, array(
'multiple' => true, 'multiple' => true,
))->getForm(); ))->setRequestHandler($requestHandler)->getForm();
$data = array( $data = array(
$this->createUploadedFileMock('abcdef', 'first.jpg', true), $this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg'),
$this->createUploadedFileMock('zyxwvu', 'second.jpg', true), $this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo2', 'foo2.jpg'),
); );
$form->setData($data); $form->setData($data);
$this->assertSame($data, $form->getData()); $this->assertSame($data, $form->getData());
} }
public function testSubmitMultiple() /**
* @dataProvider requestHandlerProvider
*/
public function testSubmitMultiple(RequestHandlerInterface $requestHandler)
{ {
$form = $this->factory->createBuilder(static::TESTED_TYPE, null, array( $form = $this->factory->createBuilder(static::TESTED_TYPE, null, array(
'multiple' => true, 'multiple' => true,
))->getForm(); ))->setRequestHandler($requestHandler)->getForm();
$data = array( $data = array(
$this->createUploadedFileMock('abcdef', 'first.jpg', true), $this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo', 'foo.jpg'),
$this->createUploadedFileMock('zyxwvu', 'second.jpg', true), $this->createUploadedFileMock($requestHandler, __DIR__.'/../../../Fixtures/foo2', 'foo2.jpg'),
); );
$form->submit($data); $form->submit($data);
@ -73,11 +87,14 @@ class FileTypeTest extends BaseTypeTest
$this->assertArrayHasKey('multiple', $view->vars['attr']); $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( $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']); $this->assertEquals('', $form->createView()->vars['value']);
@ -109,29 +126,59 @@ class FileTypeTest extends BaseTypeTest
$this->assertSame(array(), $form->getViewData()); $this->assertSame(array(), $form->getViewData());
} }
private function createUploadedFileMock($name, $originalName, $valid) /**
* @dataProvider requestHandlerProvider
*/
public function testSubmittedFilePathsAreDropped(RequestHandlerInterface $requestHandler)
{ {
$file = $this $form = $this->factory->createBuilder(static::TESTED_TYPE)->setRequestHandler($requestHandler)->getForm();
->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') $form->submit('file:///etc/passwd');
->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))
;
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,
);
} }
} }

View File

@ -51,4 +51,9 @@ class HttpFoundationRequestHandlerTest extends AbstractRequestHandlerTest
{ {
return new UploadedFile(__DIR__.'/../../Fixtures/foo'.$suffix, 'foo'.$suffix); return new UploadedFile(__DIR__.'/../../Fixtures/foo'.$suffix, 'foo'.$suffix);
} }
protected function getInvalidFile()
{
return 'file:///etc/passwd';
}
} }

View File

@ -216,4 +216,15 @@ class NativeRequestHandlerTest extends AbstractRequestHandlerTest
'size' => 100, 'size' => 100,
); );
} }
protected function getInvalidFile()
{
return array(
'name' => 'upload.txt',
'type' => 'text/plain',
'tmp_name' => 'owfdskjasdfsa',
'error' => '0',
'size' => '100',
);
}
} }

View File

@ -30,6 +30,11 @@ class JsonBundleReader implements BundleReaderInterface
{ {
$fileName = $path.'/'.$locale.'.json'; $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)) { if (!file_exists($fileName)) {
throw new ResourceBundleNotFoundException(sprintf( throw new ResourceBundleNotFoundException(sprintf(
'The resource bundle "%s" does not exist.', 'The resource bundle "%s" does not exist.',

View File

@ -30,6 +30,11 @@ class PhpBundleReader implements BundleReaderInterface
{ {
$fileName = $path.'/'.$locale.'.php'; $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)) { if (!file_exists($fileName)) {
throw new ResourceBundleNotFoundException(sprintf( throw new ResourceBundleNotFoundException(sprintf(
'The resource bundle "%s/%s.php" does not exist.', 'The resource bundle "%s/%s.php" does not exist.',

View File

@ -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',
);

View File

@ -69,4 +69,12 @@ class JsonBundleReaderTest extends TestCase
{ {
$this->reader->read(__DIR__.'/Fixtures/json', 'en_Invalid'); $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');
}
} }

View File

@ -61,4 +61,12 @@ class PhpBundleReaderTest extends TestCase
{ {
$this->reader->read(__DIR__.'/Fixtures/NotAFile', 'en'); $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');
}
} }

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Security\Csrf; 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\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
@ -20,16 +22,45 @@ use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
* Default implementation of {@link CsrfTokenManagerInterface}. * Default implementation of {@link CsrfTokenManagerInterface}.
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/ */
class CsrfTokenManager implements CsrfTokenManagerInterface class CsrfTokenManager implements CsrfTokenManagerInterface
{ {
private $generator; private $generator;
private $storage; 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->generator = $generator ?: new UriSafeTokenGenerator();
$this->storage = $storage ?: new NativeSessionTokenStorage(); $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) public function getToken($tokenId)
{ {
if ($this->storage->hasToken($tokenId)) { $namespacedId = $this->getNamespace().$tokenId;
$value = $this->storage->getToken($tokenId); if ($this->storage->hasToken($namespacedId)) {
$value = $this->storage->getToken($namespacedId);
} else { } else {
$value = $this->generator->generateToken(); $value = $this->generator->generateToken();
$this->storage->setToken($tokenId, $value); $this->storage->setToken($namespacedId, $value);
} }
return new CsrfToken($tokenId, $value); return new CsrfToken($tokenId, $value);
@ -53,9 +85,10 @@ class CsrfTokenManager implements CsrfTokenManagerInterface
*/ */
public function refreshToken($tokenId) public function refreshToken($tokenId)
{ {
$namespacedId = $this->getNamespace().$tokenId;
$value = $this->generator->generateToken(); $value = $this->generator->generateToken();
$this->storage->setToken($tokenId, $value); $this->storage->setToken($namespacedId, $value);
return new CsrfToken($tokenId, $value); return new CsrfToken($tokenId, $value);
} }
@ -65,7 +98,7 @@ class CsrfTokenManager implements CsrfTokenManagerInterface
*/ */
public function removeToken($tokenId) 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) public function isTokenValid(CsrfToken $token)
{ {
if (!$this->storage->hasToken($token->getId())) { $namespacedId = $this->getNamespace().$token->getId();
if (!$this->storage->hasToken($namespacedId)) {
return false; 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;
} }
} }

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\Security\Csrf\Tests; namespace Symfony\Component\Security\Csrf\Tests;
use PHPUnit\Framework\TestCase; 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\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManager; use Symfony\Component\Security\Csrf\CsrfTokenManager;
@ -21,145 +23,202 @@ use Symfony\Component\Security\Csrf\CsrfTokenManager;
class CsrfTokenManagerTest extends TestCase class CsrfTokenManagerTest extends TestCase
{ {
/** /**
* @var \PHPUnit_Framework_MockObject_MockObject * @dataProvider getManagerGeneratorAndStorage
*/ */
private $generator; public function testGetNonExistingToken($namespace, $manager, $storage, $generator)
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $storage;
/**
* @var CsrfTokenManager
*/
private $manager;
protected function setUp()
{ {
$this->generator = $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface')->getMock(); $storage->expects($this->once())
$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())
->method('hasToken') ->method('hasToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue(false)); ->will($this->returnValue(false));
$this->generator->expects($this->once()) $generator->expects($this->once())
->method('generateToken') ->method('generateToken')
->will($this->returnValue('TOKEN')); ->will($this->returnValue('TOKEN'));
$this->storage->expects($this->once()) $storage->expects($this->once())
->method('setToken') ->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->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
$this->assertSame('token_id', $token->getId()); $this->assertSame('token_id', $token->getId());
$this->assertSame('TOKEN', $token->getValue()); $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') ->method('hasToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->storage->expects($this->once()) $storage->expects($this->once())
->method('getToken') ->method('getToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue('TOKEN')); ->will($this->returnValue('TOKEN'));
$token = $this->manager->getToken('token_id'); $token = $manager->getToken('token_id');
$this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token); $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
$this->assertSame('token_id', $token->getId()); $this->assertSame('token_id', $token->getId());
$this->assertSame('TOKEN', $token->getValue()); $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'); ->method('hasToken');
$this->generator->expects($this->once()) $generator->expects($this->once())
->method('generateToken') ->method('generateToken')
->will($this->returnValue('TOKEN')); ->will($this->returnValue('TOKEN'));
$this->storage->expects($this->once()) $storage->expects($this->once())
->method('setToken') ->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->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
$this->assertSame('token_id', $token->getId()); $this->assertSame('token_id', $token->getId());
$this->assertSame('TOKEN', $token->getValue()); $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') ->method('hasToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->storage->expects($this->once()) $storage->expects($this->once())
->method('getToken') ->method('getToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue('TOKEN')); ->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') ->method('hasToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->storage->expects($this->once()) $storage->expects($this->once())
->method('getToken') ->method('getToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue('TOKEN')); ->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') ->method('hasToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue(false)); ->will($this->returnValue(false));
$this->storage->expects($this->never()) $storage->expects($this->never())
->method('getToken'); ->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') ->method('removeToken')
->with('token_id') ->with($namespace.'token_id')
->will($this->returnValue('REMOVED_TOKEN')); ->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']);
} }
} }

View File

@ -29,20 +29,23 @@ class HttpUtils
{ {
private $urlGenerator; private $urlGenerator;
private $urlMatcher; private $urlMatcher;
private $domainRegexp;
/** /**
* @param UrlGeneratorInterface $urlGenerator A UrlGeneratorInterface instance * @param UrlGeneratorInterface $urlGenerator A UrlGeneratorInterface instance
* @param UrlMatcherInterface|RequestMatcherInterface $urlMatcher The URL or Request matcher * @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 * @throws \InvalidArgumentException
*/ */
public function __construct(UrlGeneratorInterface $urlGenerator = null, $urlMatcher = null) public function __construct(UrlGeneratorInterface $urlGenerator = null, $urlMatcher = null, $domainRegexp = null)
{ {
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
if (null !== $urlMatcher && !$urlMatcher instanceof UrlMatcherInterface && !$urlMatcher instanceof RequestMatcherInterface) { if (null !== $urlMatcher && !$urlMatcher instanceof UrlMatcherInterface && !$urlMatcher instanceof RequestMatcherInterface) {
throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.');
} }
$this->urlMatcher = $urlMatcher; $this->urlMatcher = $urlMatcher;
$this->domainRegexp = $domainRegexp;
} }
/** /**
@ -56,6 +59,10 @@ class HttpUtils
*/ */
public function createRedirectResponse(Request $request, $path, $status = 302) 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); return new RedirectResponse($this->generateUri($request, $path), $status);
} }

View File

@ -38,6 +38,38 @@ class HttpUtilsTest extends TestCase
$this->assertTrue($response->isRedirect('http://symfony.com/')); $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() public function testCreateRedirectResponseWithRouteName()
{ {
$utils = new HttpUtils($urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock()); $utils = new HttpUtils($urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock());