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:
commit
caa10ae038
@ -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 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)
|
||||
|
||||
* bug #24888 [FrameworkBundle] Specifically inject the debug dispatcher in the collector (ogizanagi)
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -14,8 +14,10 @@ namespace Symfony\Bundle\SecurityBundle;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory;
|
||||
use Symfony\Component\Console\Application;
|
||||
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;
|
||||
@ -58,6 +60,7 @@ class SecurityBundle extends Bundle
|
||||
$extension->addUserProviderFactory(new InMemoryFactory());
|
||||
$extension->addUserProviderFactory(new LdapFactory());
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
}
|
||||
|
||||
public function registerCommands(Application $application)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -127,15 +127,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) {
|
||||
|
@ -57,6 +57,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,13 +27,29 @@ class FileType extends AbstractType
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
if ($options['multiple']) {
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
|
||||
$form = $event->getForm();
|
||||
$data = $event->getData();
|
||||
$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);
|
||||
} elseif (!$requestHandler->isFileUpload($event->getData())) {
|
||||
$emptyData = $form->getConfig()->getEmptyData();
|
||||
|
||||
$data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData;
|
||||
@ -41,7 +57,6 @@ class FileType extends AbstractType
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -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