Merge branch '4.0' into 4.1

* 4.0:
  do not mock the session in token storage tests
  [DependencyInjection] resolve array env vars
  Add Occitan plural rule
  Fix security/* cross-dependencies
  [Lock] Skip test if posix extension is not installed
  [DI] Allow defining bindings on ChildDefinition
  use strict compare in url validator
  Disallow illegal characters like "." in session.name
  [HttpKernel] do file_exists() check instead of silent notice
  fix rounding from string
This commit is contained in:
Fabien Potencier 2018-05-21 12:10:11 +02:00
commit af4372220c
16 changed files with 238 additions and 182 deletions

View File

@ -24,6 +24,7 @@
"require-dev": {
"symfony/stopwatch": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
<<<<<<< HEAD
"symfony/form": "~3.4|~4.0",
"symfony/http-kernel": "~3.4|~4.0",
"symfony/property-access": "~3.4|~4.0",
@ -33,6 +34,17 @@
"symfony/expression-language": "~3.4|~4.0",
"symfony/validator": "~3.4|~4.0",
"symfony/translation": "~3.4|~4.0",
=======
"symfony/form": "^3.3.10|~4.0",
"symfony/http-kernel": "~2.8|~3.0|~4.0",
"symfony/property-access": "~2.8|~3.0|~4.0",
"symfony/property-info": "~2.8|3.0|~4.0",
"symfony/proxy-manager-bridge": "~2.8|~3.0|~4.0",
"symfony/security": "^2.8.31|^3.3.13|~4.0",
"symfony/expression-language": "~2.8|~3.0|~4.0",
"symfony/validator": "^3.2.5|~4.0",
"symfony/translation": "~2.8|~3.0|~4.0",
>>>>>>> 3.4
"doctrine/data-fixtures": "1.0.*",
"doctrine/dbal": "~2.4",
"doctrine/orm": "^2.4.5"

View File

@ -467,7 +467,16 @@ class Configuration implements ConfigurationInterface
->children()
->scalarNode('storage_id')->defaultValue('session.storage.native')->end()
->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end()
->scalarNode('name')->end()
->scalarNode('name')
->validate()
->ifTrue(function ($v) {
parse_str($v, $parsed);
return implode('&', array_keys($parsed)) !== (string) $v;
})
->thenInvalid('Session name %s contains illegal character(s)')
->end()
->end()
->scalarNode('cookie_lifetime')->end()
->scalarNode('cookie_path')->end()
->scalarNode('cookie_domain')->end()

View File

@ -46,6 +46,105 @@ class ConfigurationTest extends TestCase
$this->assertEquals(array('FrameworkBundle:Form'), $config['templating']['form']['resources']);
}
public function getTestValidSessionName()
{
return array(
array(null),
array('PHPSESSID'),
array('a&b'),
array(',_-!@#$%^*(){}:<>/?'),
);
}
/**
* @dataProvider getTestInvalidSessionName
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testInvalidSessionName($sessionName)
{
$processor = new Processor();
$processor->processConfiguration(
new Configuration(true),
array(array('session' => array('name' => $sessionName)))
);
}
public function getTestInvalidSessionName()
{
return array(
array('a.b'),
array('a['),
array('a[]'),
array('a[b]'),
array('a=b'),
array('a+b'),
);
}
/**
* @dataProvider getTestValidTrustedProxiesData
*/
public function testValidTrustedProxies($trustedProxies, $processedProxies)
{
$processor = new Processor();
$configuration = new Configuration(true);
$config = $processor->processConfiguration($configuration, array(array(
'secret' => 's3cr3t',
'trusted_proxies' => $trustedProxies,
)));
$this->assertEquals($processedProxies, $config['trusted_proxies']);
}
public function getTestValidTrustedProxiesData()
{
return array(
array(array('127.0.0.1'), array('127.0.0.1')),
array(array('::1'), array('::1')),
array(array('127.0.0.1', '::1'), array('127.0.0.1', '::1')),
array(null, array()),
array(false, array()),
array(array(), array()),
array(array('10.0.0.0/8'), array('10.0.0.0/8')),
array(array('::ffff:0:0/96'), array('::ffff:0:0/96')),
array(array('0.0.0.0/0'), array('0.0.0.0/0')),
);
}
/**
* @group legacy
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testInvalidTypeTrustedProxies()
{
$processor = new Processor();
$configuration = new Configuration(true);
$processor->processConfiguration($configuration, array(
array(
'secret' => 's3cr3t',
'trusted_proxies' => 'Not an IP address',
),
));
}
/**
* @group legacy
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testInvalidValueTrustedProxies()
{
$processor = new Processor();
$configuration = new Configuration(true);
$processor->processConfiguration($configuration, array(
array(
'secret' => 's3cr3t',
'trusted_proxies' => array('Not an IP address'),
),
));
}
>>>>>>> 3.4
public function testAssetsCanBeEnabled()
{
$processor = new Processor();

View File

@ -121,12 +121,4 @@ class ChildDefinition extends Definition
{
throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.');
}
/**
* @internal
*/
public function setBindings(array $bindings)
{
throw new BadMethodCallException('A ChildDefinition cannot have bindings set on it.');
}
}

View File

@ -100,7 +100,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass
$def->setAutowired($parentDef->isAutowired());
$def->setChanges($parentDef->getChanges());
$def->setBindings($parentDef->getBindings());
$def->setBindings($definition->getBindings() + $parentDef->getBindings());
// overwrite with values specified in the decorator
$changes = $definition->getChanges();

View File

@ -1363,6 +1363,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
$completed = false;
foreach ($envPlaceholders as $env => $placeholders) {
foreach ($placeholders as $placeholder) {
if (false !== stripos($value, $placeholder)) {
@ -1373,14 +1374,19 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
if ($placeholder === $value) {
$value = $resolved;
$completed = true;
} else {
if (!is_string($resolved) && !is_numeric($resolved)) {
throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type %s inside string value "%s".', $env, gettype($resolved), $value));
throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type %s inside string value "%s".', $env, gettype($resolved), $this->resolveEnvPlaceholders($value)));
}
$value = str_ireplace($placeholder, $resolved, $value);
}
$usedEnvs[$env] = $env;
$this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1;
if ($completed) {
break 2;
}
}
}
}

View File

@ -355,6 +355,27 @@ class ResolveChildDefinitionsPassTest extends TestCase
$this->assertSame(array(2, 1, 'foo' => 3), $def->getArguments());
}
public function testBindings()
{
$container = new ContainerBuilder();
$container->register('parent', 'stdClass')
->setBindings(array('a' => '1', 'b' => '2'))
;
$child = $container->setDefinition('child', new ChildDefinition('parent'))
->setBindings(array('b' => 'B', 'c' => 'C'))
;
$this->process($container);
$bindings = array();
foreach ($container->getDefinition('child')->getBindings() as $k => $v) {
$bindings[$k] = $v->getValues()[0];
}
$this->assertEquals(array('b' => 'B', 'c' => 'C', 'a' => '1'), $bindings);
}
public function testSetAutoconfiguredOnServiceIsParent()
{
$container = new ContainerBuilder();

View File

@ -665,17 +665,49 @@ class ContainerBuilderTest extends TestCase
putenv('DUMMY_ENV_VAR');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage A string value must be composed of strings and/or numbers, but found parameter "env(ARRAY)" of type array inside string value "ABC %env(ARRAY)%".
*/
public function testCompileWithArrayResolveEnv()
{
$bag = new TestingEnvPlaceholderParameterBag();
$container = new ContainerBuilder($bag);
$container->setParameter('foo', '%env(ARRAY)%');
$container->setParameter('bar', 'ABC %env(ARRAY)%');
putenv('ARRAY={"foo":"bar"}');
$container = new ContainerBuilder();
$container->setParameter('foo', '%env(json:ARRAY)%');
$container->compile(true);
$this->assertSame(array('foo' => 'bar'), $container->getParameter('foo'));
putenv('ARRAY');
}
public function testCompileWithArrayAndAnotherResolveEnv()
{
putenv('DUMMY_ENV_VAR=abc');
putenv('ARRAY={"foo":"bar"}');
$container = new ContainerBuilder();
$container->setParameter('foo', '%env(json:ARRAY)%');
$container->setParameter('bar', '%env(DUMMY_ENV_VAR)%');
$container->compile(true);
$this->assertSame(array('foo' => 'bar'), $container->getParameter('foo'));
$this->assertSame('abc', $container->getParameter('bar'));
putenv('DUMMY_ENV_VAR');
putenv('ARRAY');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage A string value must be composed of strings and/or numbers, but found parameter "env(json:ARRAY)" of type array inside string value "ABC %env(json:ARRAY)%".
*/
public function testCompileWithArrayInStringResolveEnv()
{
putenv('ARRAY={"foo":"bar"}');
$container = new ContainerBuilder();
$container->setParameter('foo', 'ABC %env(json:ARRAY)%');
$container->compile(true);
putenv('ARRAY');
}
/**
@ -1415,11 +1447,3 @@ class B
{
}
}
class TestingEnvPlaceholderParameterBag extends EnvPlaceholderParameterBag
{
public function get($name)
{
return 'env(array)' === strtolower($name) ? array(123) : parent::get($name);
}
}

View File

@ -78,6 +78,16 @@ class MoneyToLocalizedStringTransformerTest extends TestCase
$transformer = new MoneyToLocalizedStringTransformer(null, null, null, 100);
IntlTestHelper::requireFullIntl($this, false);
\Locale::setDefault('de_AT');
$this->assertSame(3655, (int) $transformer->reverseTransform('36,55'));
}
public function testFloatToIntConversionMismatchOnTransform()
{
$transformer = new MoneyToLocalizedStringTransformer(null, null, MoneyToLocalizedStringTransformer::ROUND_DOWN, 100);
IntlTestHelper::requireFullIntl($this, false);
\Locale::setDefault('de_AT');
$this->assertSame('10,20', $transformer->transform(1020));
}
}

View File

@ -465,7 +465,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
$fresh = $oldContainer = false;
try {
if (\is_object($this->container = include $cache->getPath())) {
if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) {
$this->container->set('kernel', $this);
$oldContainer = $this->container;
$fresh = true;
@ -528,7 +528,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
}
}
if (null === $oldContainer) {
if (null === $oldContainer && file_exists($cache->getPath())) {
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
try {
$oldContainer = include $cache->getPath();

View File

@ -710,6 +710,7 @@ class NumberFormatter
} elseif (isset(self::$customRoundingList[$roundingModeAttribute])) {
$roundingCoef = pow(10, $precision);
$value *= $roundingCoef;
$value = (float) (string) $value;
switch ($roundingModeAttribute) {
case self::ROUND_CEILING:

View File

@ -428,6 +428,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
// array(1.125, '1.13'),
array(1.127, '1.13'),
array(1.129, '1.13'),
array(1020 / 100, '10.20'),
);
}
@ -451,6 +452,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(1.125, '1.12'),
array(1.127, '1.13'),
array(1.129, '1.13'),
array(1020 / 100, '10.20'),
);
}
@ -474,6 +476,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(1.125, '1.12'),
array(1.127, '1.13'),
array(1.129, '1.13'),
array(1020 / 100, '10.20'),
);
}
@ -498,6 +501,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.12'),
array(-1.125, '-1.12'),
array(-1.127, '-1.12'),
array(1020 / 100, '10.20'),
);
}
@ -522,6 +526,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.13'),
array(-1.125, '-1.13'),
array(-1.127, '-1.13'),
array(1020 / 100, '10.20'),
);
}
@ -546,6 +551,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.12'),
array(-1.125, '-1.12'),
array(-1.127, '-1.12'),
array(1020 / 100, '10.20'),
);
}
@ -570,6 +576,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.13'),
array(-1.125, '-1.13'),
array(-1.127, '-1.13'),
array(1020 / 100, '10.20'),
);
}

View File

@ -31,6 +31,7 @@ trait BlockingStoreTestTrait
* This test is time sensible: the $clockDelay could be adjust.
*
* @requires extension pcntl
* @requires extension posix
* @requires function pcntl_sigwaitinfo
*/
public function testBlockingLocks()

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\Security\Csrf\Tests\TokenStorage;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
/**
@ -22,7 +24,7 @@ class SessionTokenStorageTest extends TestCase
const SESSION_NAMESPACE = 'foobar';
/**
* @var \PHPUnit_Framework_MockObject_MockObject
* @var Session
*/
private $session;
@ -33,118 +35,53 @@ class SessionTokenStorageTest extends TestCase
protected function setUp()
{
$this->session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')
->disableOriginalConstructor()
->getMock();
$this->session = new Session(new MockArraySessionStorage());
$this->storage = new SessionTokenStorage($this->session, self::SESSION_NAMESPACE);
}
public function testStoreTokenInClosedSession()
public function testStoreTokenInNotStartedSessionStartsTheSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('set')
->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->storage->setToken('token_id', 'TOKEN');
$this->assertTrue($this->session->isStarted());
}
public function testStoreTokenInActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('set')
->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->session->start();
$this->storage->setToken('token_id', 'TOKEN');
$this->assertSame('TOKEN', $this->session->get(self::SESSION_NAMESPACE.'/token_id'));
}
public function testCheckTokenInClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->storage->hasToken('token_id'));
$this->assertTrue($this->storage->hasToken('token_id'));
$this->assertTrue($this->session->isStarted());
}
public function testCheckTokenInActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->start();
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->storage->hasToken('token_id'));
$this->assertTrue($this->storage->hasToken('token_id'));
}
public function testGetExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(true));
$this->session->expects($this->once())
->method('get')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->assertSame('RESULT', $this->storage->getToken('token_id'));
$this->assertTrue($this->session->isStarted());
}
public function testGetExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(true));
$this->session->expects($this->once())
->method('get')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->session->start();
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->assertSame('RESULT', $this->storage->getToken('token_id'));
}
@ -154,18 +91,6 @@ class SessionTokenStorageTest extends TestCase
*/
public function testGetNonExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(false));
$this->storage->getToken('token_id');
}
@ -174,85 +99,33 @@ class SessionTokenStorageTest extends TestCase
*/
public function testGetNonExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(false));
$this->session->start();
$this->storage->getToken('token_id');
}
public function testRemoveNonExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(null));
$this->assertNull($this->storage->removeToken('token_id'));
}
public function testRemoveNonExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(null));
$this->session->start();
$this->assertNull($this->storage->removeToken('token_id'));
}
public function testRemoveExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('TOKEN'));
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
}
public function testRemoveExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('TOKEN'));
$this->session->start();
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
}

View File

@ -107,6 +107,7 @@ class PluralizationRules
case 'nl':
case 'nn':
case 'no':
case 'oc':
case 'om':
case 'or':
case 'pa':

View File

@ -87,7 +87,7 @@ class UrlValidator extends ConstraintValidator
Url::CHECK_DNS_TYPE_SOA,
Url::CHECK_DNS_TYPE_SRV,
Url::CHECK_DNS_TYPE_TXT,
))) {
), true)) {
throw new InvalidOptionsException(sprintf('Invalid value for option "checkDNS" in constraint %s', get_class($constraint)), array('checkDNS'));
}